文章目录
并发编程面试文章地址链接
内容 | 博客链接 |
---|---|
并发编程面试题之常见面试题 | https://blog.csdn.net/weixin_38251871/article/details/104658674 |
并发编程面试题之 volatile 关键字 |
https://blog.csdn.net/weixin_38251871/article/details/104667384 |
并发编程面试题之 CAS |
https://blog.csdn.net/weixin_38251871/article/details/104667406 |
并发编程面试题之锁 | https://blog.csdn.net/weixin_38251871/article/details/104667392 |
并发编程面试题之阻塞队列 | 待完成… |
并发编程面试题之 AQS |
待完成… |
并发编程面试题之线程池 | 待完成… |
并发编程面试题之 synchronized 和 ReentrantLock 的区别 |
https://blog.csdn.net/weixin_38251871/article/details/104667532 |
并发编程面试题之 CyclicBarrier、CountDownLatch、Semaphore |
待完成… |
并发编程面试题之 ConcurrentHashMap |
https://blog.csdn.net/weixin_38251871/article/details/104667433 |
并发编程面试题之 synchronized 实现原理 |
https://blog.csdn.net/weixin_38251871/article/details/104667415 |
线程与进程的区别
- 进程是操作系统
分配资源
的最小单元,线程是操作系统调度
的最小单元,也可以说是进程里的一个执行单元 - 一个程序至少有一个进程,一个进程至少有一个线程
线程的生命周期
新建【NEW】:
当程序使用new
关键字创建了一个Thread
对象后,该线程就处于新建状态,此时的 JVM 为其分配内存,并且初始化其成员变量的值;例Thread t1 = new Thread()
就绪状态/可运行状态【RUNNABLE】:
当线程对象调用start()
方法后,该线程就处于可运行状态,此时的 JVM 会为其创建方法调用栈和程序计数器,等获取到 CPU 后执行运行状态【RUNNING】: Thread 类里面没有这个状态
在就绪状态的线程获取到了 CPU 的使用权时候开始执行run()
方法,该线程就处于运行状态阻塞状态【BLOCKED】:
线程因为某种原因放弃了 CPU 的使用权,也让出了 cpu 时间片,暂时的停止运行直到线程再次获取到 CPU 的使用权,才有机会再次获得 CPU 的时间片转到运行状态。在同步阻塞的情况下,在获取到对象的同步锁时,如果该同步锁被其他的线程所占用,JVM 会把该线程放入到锁池中,当线程获取到锁的时,线程重新转为可运行状态等待状态【WAITING】:
运行状态的线程执行wait()
方法后, JVM 会把该线程放入等待队列限时等待【TIMED_WAITING】:
运行状态的线程执行sleep()/join()
方法后进入到限时等待状态,当sleep() 超时 / join() 等待线程终止
后,线程重新转为可运行状态终止【TERMINATED】:
线程的创建方式
- 继承 Thread 类
- 实现 Runnable 接口
- 实现 Callable 接口,需要实现的是 call() 方法
- 使用线程池
如何停止正在运行的线程
-
正常地运行结束
-
使用退出标志退出线程,使用
boolean
类型的标志,并通过这个标志的为false/true
来控制while
循环是否退出,同时需要使用volatile
关键字进行修饰,这个关键的目的是使标志同步,并且修改标志位的值时让其他线程可见 -
interrupt()
结束线程-
如果一个线程由于等待某些事件的发生而阻塞【例:Thread.sleep()/Thread.join()/IO 等待时】,当调用
Thread.interrupt()
方法时,会抛出InterruptException
异常,通过代码捕获该异常,然后break
跳出循环。 -
如果一个线程未处于阻塞状态:使用
isInterrupted()
方法判断线程的中断标志来退出循环,当使用interrupt()
方法的时候中断标志会设置为true
,类似自定义标志退出循环
-
-
stop()
方法停止线程【不推荐】: Thread 类里面也提供了stop()
方法来停止线程,但是该方法是很危险的,就像是突然关闭计算机,可能会产生不可预料的后果。在调用Thread#stop()
方法之后,创建子线程的线程就会抛出ThreadDeathError
的错误,并且会释放子线程的所有锁,一般情况下对加锁的代码都是为了保证数据的一致性,突然释放所有的锁可能被保护的数据出现不一致性。
wait() 方法与 sleep() 方法的区别
sleep()
方法是线程类Thread
的静态方法,调用此方法设置休眠时间,会让出CPU
的使用权给其他线程,但还是持有对象锁,当休眠时间结束时,该线程会回到就绪状态【RUNNABLE
】wait()
方法是 Object 类中的方法,调用Object#wait()
方法后会导致当前线程放弃对象锁(线程暂停执行),也会让出CPU
的使用权给其他线程,进入到等待池,只有针对此对象调用Object#notify()
方法后该线程才准备获取对象锁进入运行状态【RUNNING
】
为什么需要把 wait()/notify() 放在 synchronized 代码块里?
- JAVA 的 API 强制要求放置在同步块中,如果不这样做会抛出 IllegalMonitorStateException 异常,还有一个原因是为了避免
Object#wait()、Object#notify()
之间产生竞态条件
start() 与 run() 方法的区别
start()
方法是真正启动一个线程,实现了多线程运行,调用了start()
方法之后,此线程就进入到了就绪状态【RUNNABLE
】run()
方法里称之为线程体,包含了要执行这个线程的内容,线程就进入到了运行状态【RUNNING
】,开始运行run()
方法中的代码
ThreadLocal
ThreadLocal
又称之为线程本地变量/线程本地存储
, 是一个线程本地变量的工具类, 主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用, 减少同一线程内多个方法或者组件之间一些公共变量传递的复杂度
ThreadLocal
其实是一种以空间换时间的做法, 在每个Thread
里面维护了一个ThreadLocal.ThreadLocalMap
, 把数据进行了隔离, 数据不进行共享, 自然就没有线程安全问题
- 每个线程都有自己的一个
ThreadLocalMap
类对象, 可以将线程自己的对象保存在其中, 各个都是独立的, 线程可以正确访问到自己的对象 - 将一个静态的
ThreadLocal
静态实例作为 key, 将不同对象的引用保存到不同线程的ThreadLocalMap
中, 然后在线程执行的各处通过这个静态ThreadLocal#get()
方法取得自己线程保存的那个对象, 避免了这个对象作为参数传递的麻烦 - 一般情况下的使用场景是用来解决
数据库连接
或者session管理
等