多线程
1.java线程内存模型JMM
JMM数据原子操作
-
read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
-
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
-
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
-
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
-
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
-
write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
-
lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
-
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
-
MESI缓存一致性协议:
多个cpu从主内存读取同一个数据到各自的告诉缓存,当其中某个cpu修改了缓存的数据,该数据会马上同步回主内存,其他cpu通过***总线嗅探机制*** 可以感知到数据的变化从而将自己缓存的数据失效
-
volatile缓存可见性实现原理
通过汇编lock前缀指令,他会锁定这块内存区域的缓存,并回写到主内存
1)将当前处理器缓存行的数据立即写回到系统内存
2)这个写回内存的操作会引起在其他cpu里的缓存该地址的数据无效(MESI协议)
可保证可见性、有序性。但是不能保证原子性。原子性需要synchronized保证。
2.锁
synchronized锁的是对象,静态方法锁的是A.class(锁的类)
synchronized互斥锁、悲观锁、同步锁(1.6之前重量级锁,)
重量级锁:线程阻塞、上下文切换、操作系统线程调度、内核态用户态状态切换
3.CAS
compareAndSet
、 compareAndSwap
一般cas都会配一个while循环,设置值成功,跳出循环。
CAS先读取、然后修改回写的过程中先跟原来的值对比,如果跟原来值一直则修改,如果不一致则重新读取在修改。
操作系统底层的汇编指令:lock
和 cmpxchgq
,所以保证了原子性(缓存行锁、总线锁)
ABA问题,在读取了之后,修改了,比如从0到1又被某一个线程改为0了。
解决方案:布尔类型(是否改过)、version加版本号是否修改过。
`AtomicStampedReference` 的`compareAndSet`方法感知是否修改
CAS: 无锁、自旋锁、乐观锁、轻量级锁
4.锁优化
偏向锁
优化50、60%的synchronized锁都是单线程的,不需要每个都消耗大量的系统资源,所以存当前的线程id,如果下次进入锁的线程还是这个线程,直接不加锁。创建对象后,jvm默认有个4秒的延迟,开启偏向锁。如果不用偏向锁,可以通过-XX:UseBiasedLocking = false
来设置。偏向锁在synchronized结束后,不会释放偏向锁
轻量级锁
锁升级 -> 多个线程加锁CAS轻度竞争,则升级为轻量级锁(即自旋锁 while)
重量级锁
重度竞争的时候,不让大量轻量级锁自旋,空转,耗资源。所以升级为重量级锁 指针指向monitor
分段CAS
LongAdder
5.对象内部组成结构
6.线程状态
7.常用API
interrupt可以打断sleep的线程,如果打断会抛出异常:InterruptedException
8.sleep和yield区别:
sleep 线程睡眠,不会被任务调度器调度执行,只有阻塞时间结束才能等待调度。
yield 线程让出执行权,还是有机会被任务调度器调度执行,所以有可能立刻再次执行。