1、线程状态
Java线程有6中状态,分别是:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,以及TERMINATED
其中线程状态切换图如下:
- 线程start后,从NEW -> RUNNABLE;
- 如果线程运行需要锁,则等待锁时,线程状态为BLOCKED,获取到锁后BLOCKED -> RUNNABLE;
- 线程调用wait之后,如果有设定超时时间则RUNNABLE -> TIMED_WAITING,
否则RUNNABLE -> WAITING; - WAITING状态的线程被notify后,WAITING -> RUNNABLE;
- TIMED_WAITING状态线程,在被notify或超时时,TIMED_WAITING -> RUNNABLE;
- 线程任务结束,RUNNABLE -> TERMINATED.
2、线程终止
线程终止方法:
- stop:强制终止线程,并且清除监控器锁的信息,JDK中不建议使用
不管是否线程是否占用锁,都会强制终止,所以可能会导致线程不安全的问题。 - interrupt:不会强制终止,保证线程安全
如果目标线程在调用wait,join,或者sleep方法时被阻塞,interrupt会立即生效,并抛出
InterruptedException异常。可以通过捕获InterruptedException异常,进行相应的业务处理。
如果目标线程时被I/O或者NIO的Channel所阻塞,同样,I/O操作会被中断,或者返回特殊异常值。达到终止线程的目的。 - 使用标志位实现线程终止
3、线程通信
- suspend & resume
已弃用。原因:容易引发死锁
同步代码块中使用,suspend调用后,不会释放锁 -> 死锁
suspend在resume后调用 -> 死锁 - wait & notify
wait和notify的调用顺序非常重要,如果wait在notify后进行调用,则线程会一直处在WAITING状态。
wait,notify,notifyAll只能由同一对象锁的持有者线程调用,也就是要写在同步块里面,且调用后会释放锁。如果不在同步块中使用wait,notify和notifyAll会抛出IllegalMonitorStateException异常。 - park & unpark
LockSupport工具类提供的方法。令牌机制。
park等待令牌,unpark提供令牌,令牌只能存在一个
不要求park和unpark方法的调用顺序。多次调用unpark之后,再调用park,线程会直接运行。
但不会叠加,也就是说,多次调用park方法,第一次会拿到令牌,直接运行,后续调用会进行等待。
park/unpark不是基于锁监视器实现的,所以如果在同步代码块中,不会释放锁 -> 死锁
unpark指定到线程
Thread thread = new Thread(new Runnable() {
public void run() {
LockSupport.park();
}
});
thread.start();
LockSupport.unpark(thread);
重点提醒:
伪唤醒: 线程并非因为notify、notifyAll、unpark等api调用而唤醒,是更底层原因导致的
官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会受到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出
4、线程私有
线程封闭:封闭对应开放
- 开放:共享数据,共享变量,会涉及线程安全,以及同步数据的问题
- 封闭:数据封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术成为线程封闭。在并发模式下是线程安全的。
线程封闭具体的体现有:ThreadLocal,局部变量
- ThreadLocal
ThreadLocal var = new ThreadLocal()
线程级别的变量,会自动在每一个线程上创建一个T的副本,副本之间彼此独立,不相互影响。在并发模式下是绝对安全的变量
可以用ThreadLocal存储一些参数,一遍在线程中多个方法中使用,用来替代方法传参的问题
可以理解为JVM维护了一个Map<Thread, T>,每个线程要用到这个变量T的时候,用当前的线程去Map中取,取出来的都是不一样的。 - 局部变量
局部变量的固有属性之一就是封闭在线程中的。
它们位于执行线程的栈中,其他线程无法访问这个栈。