线程的六种状态
- New: 已创建但尚未启动的线程状态。
- Runnable: 可运行线程的线程状态,等待CPU调度。
- Blocked: 线程阻塞等待监视器锁定的线程状态。处于synchronize同步代码块或方法中被阻塞。
- Waiting: 等待线程的线程状态。下列不带超时的方式: Object.wait、Thread.join、LockSupport.park。
- Time Waiting: 具有指定等待时间的等待线程的线程状态。下列带超时的方式: Thread.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUntil。
- Terminated: 终止线程的线程状态。线程真正常完成执行或者出现异常。
注:
1.Waiting状态可理解成主动立即让线程等待。
2.Timed Waiting状态可理解成代码超时等待。
3.Blocked状态是在线程拿不到锁(如:在抢synchronized的时候),但在等待抢lock锁的时候是Waiting状态。
4.线程执行完毕或者执行stop、interrupt犯法后会处于Terminated状态,且不能再star。
线程状态切换演示
-
New -> Runnable -> Time Waiting -> Runnable -> Terminated
-
New -> Runnable -> Blocked -> Runnable -> Terminated
线程中止
-
Thread.stop是不正确的线程中止方式,可能导致线程安全问题,会强制中止线程,已废弃。
-
Thread.interrupt才是推荐的方式。将stop改为interrupt后,最终输出“i=1 j=2”,数据一致。interrupt方法会抛InterruptedException,不会存在线程安全问题。
-
标志位方式。
线程通信
要想实现多个线程之间的协同,如:线程执行先后顺序,获取某个线程执行的结果等等。
涉及到线程之间相互通信,分为下面四类:文件共享、网络共享、变量共享以及JDK提供的线程协调API
-
文件共享
-
网络共享
(和文件共享类似) -
变量共享
也就是【堆内存】中的变量是线程共享变量。
-
jdk提供的线程协调API
-
wait/notify
这对方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出ilegalMonitorStateException异常。
wait方法会当前线程等待,加入该对象的等待集合中,并且释放对象锁。
notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。
虽然wait会释放锁,但是对顺序有要求,如果在notify被调用后才开始执行wait方法,线程会永远处于Waiting状态。
-
park/unpark
线程调用park则等待“许可”,调用unpark会为执行线程提供“许可”。
相比wait/notify,不要求park和unpark方法的调用顺序。可以多次调用unpark之后再调用park,线程也会直接运行,但不会叠加,也就是说,连续多次调用park方法,第一次会拿到“许可”直接运行,后续调用会进入等待。
park不释放锁,所以park/unpark不要写在同步代码块(会导致死锁),和wait/notify正好相反。
注:如果线程B连续调用了多次unpark函数,当线程A调用一次park函数就使用掉这个“许可”继续执行,如果线程A第二次再调用park,则进入等待状态。(unpark多次只需要一次park) -
suspend/resume(已废弃)
suspend/resume是线程Thread的方法,但是被废弃,因为suspend也是不释放锁,很容易写出死锁代码(兼容了wait/notify和park/unpark的缺点,既不能写在同步代码块,又不能颠倒执行顺序)。
-
-
伪唤醒
线程封闭
-
线程封闭概念
多线程访问共享可变数据时,涉及到线程间数据同步的问题。并不是所有时候都要用到共享数据,所以线程封闭的概念就提出来了,。
数据都被封闭在各自的线程之中就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭。
线程封闭具体的体现有:ThreadLocal、局部变量。 -
ThreadLocal
ThreadLocal是Java里的一种特殊变量。它是一个线程级别变量,每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。
用法:ThreadLocal
会自动在每个线程上创建一个T的副本,副本之间彼此独立,互不影响。
可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法。
可以简单理解为:JVM维护了一个Map<Thread,T>,每个线程要用这个T的时候,就用当前的线程去Map里面取。
- 栈封闭
局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的【虚拟机栈】中,其他线程无法访问这个栈。
局部变量可以理解成Java类中方法的变量,保存在【虚拟机栈】中的栈帧中的本地局部变量表
CPU内存屏障
-
CPU性能优化手段一一缓存
- 多级缓存
CPU高速缓存分为:L1、L2、L3等级,CPU读取顺序依次是:L1>L2>L3>内存>硬盘。 - 缓存同步协议
多核CPU读取同样的数据进行缓存。
MESI协议,它规定每条缓存有个状态位,同时定义了四个状态:修改态(Modified)、专有态(Exclusive)、共享态(Shared)、无效态(Invalid)。简单来说:CPU要处理控制自己的读写操作,也要监听其他CPU发出的通知,保证了多核CPU数据的一致。
- 多级缓存
-
CPU性能优化手段一一运行时指令重排
-
CPU高速缓存和CPU指令重排两种优化手段都存在问题
- CPU缓存和内存中的数据不是实时同步的,可能存在同一时间点,各个CPU可能读取同一块内存地址的值不一致。
- 多核多线程的CPU中,指令还是可能出现超出as-is-serial允许以外的乱序现象。
-
内存屏障
处理器提供了两种内存屏障(Memory Barrier)指令解决了以上两个问题- 写内存屏障:在指令后插入Store Barrier,强制写入主内存,让其他线程可见,并且不能发生重排。
- 读内存屏障:在指令前插入Load Barrier,让高速缓存失效,强制重新从主内存加载数据。
-
结语
无论MESI缓存协议的实现还是内存屏障的性能问题都体现在不同CPU厂家在优化中做出的努力,不同CPU厂商所付出的人力物力成本,最终体现在不同CPU性能差距上。