文章目录
1. Wait set(wait队列)
- 名称解析:当对于同一个锁而言,会把所有的wait线程,加入到自己的一个wait set中。
- 注意点:
- 所有对象都会有一个wait set,用于存放调用了该对象的wait方法之后进入block状态的线程。
- 线程被notify之后,不一定立即执行,需要再次去抢锁,才能获得CPU执行权。
- 线程从wait set中被唤醒的顺序不一定是FIFO。
- 线程被唤醒后,必须重新获取锁。 然后,跳转到wait之后的代码接着执行。
2. JMM
1. 缓存一致性:
- 概念
- 每个CPU都会使用cache一部分数据。
- java会对于只有读操作的线程,只在线程启动时去主存获取数据;而对于有写操作的线程则会去内存中多次获取数据以及更新数据。
- 因此会导致数据的不一致。
- 解决方法:
- 给数据总线加锁;总线分为(数据总线、控制总线、地址总线);硬件之间的通信是通过总线来传递数据的,因此,CPU与内存之间也是通过总线通信。所以,Lock住总线,就保证修改数据的唯一性(在同一时刻,只有一个线程能够修改数据)。
- CPU高速缓存一致性协议(MESI)。 保证多线程之间获取到的数据是准确的,是互相可见的。
- 核心思想:
1. 当CPU写入数据的时候,如果发现该变量被共享(也就是在其它CPU中存在该变量的副本),会发出一个信号,通知其它CPU该变量缓存无效。
2. 当其它其它CPU访问该变量的时候,重新到主内存中获取数据。
2. 重排序:
保证最终一致性,而且,变量之间如果有依赖关系,则不能重排序,无关的变量可能会发生重排序。
3. java线程安全三原则
-
原子性:
- 定义: java规范中,规定对于基本数据类型的变量读取和赋值是原子性的,要么都成功,要么都失败,这些操作不可被打断。 但对于long和double需要注意CPU位数,若在32位的机器上也可能不是原子性的,因为long和double是64位的;可能会把高32位和低32位分开存储。
- 事例:
- a=10; 原子性,只是一个读取操作。
- b=a; 不满足,1. 读取a; 2. 把a赋值给b。
- c++; 不满足,1. 读取c;2. c+1;3. 把值赋给c;
- c=c+1;和3一致。
-
可见性:
- 定义:在java中,使用Volatile关键字修饰的变量;当此变量发生改变,所有CPU缓存中的此变量失效,都得到主内存中去获取最新的变量;但是,Volatile关键字无法保证原子性。
-
有序性:
- 定义:最关键的是,需要保证最终一致性原则。
- 规则:
- 代码的执行顺序:编写在前面的,发生在编写后面的; eg: b=a+1; 操作的时候是先a+1;再是赋值给b;
- unlock必须发生在lock之后。
- Volatile修饰的变量,对于一个变量的写操作先于对改变量的读操作。
- 传递规则:操作A优先于B,B优先于C,那么A肯定优先于C;
3. volatile
1. 语义
- 一旦使用Volatile修饰某个变量的时候,此变量就具备了两次语义。
- 保证了不同线程之间的可见性。
- 禁止对其进行重排序,也就是保证了有序性。
- 并未保证原子性。
2. 使用场景
- 状态量。必须使用volatile关键字;
- 因为保证代码的有序性;保证Volatile变量任何使用地方之前的代码是执行完了的,之后的代码得等Volatile变量执行完,才能执行。
3. 底层原理
- 只有在多核处理器上,才会对总线加Lock前缀;在单核CPU上不需要LOCK;
- Intel在早起是在总线上增加Lock前缀;后期改为使用缓存锁来保证指令执行的原子性。提高了Lock前缀指令的执行开销。
4. final域的内存语义
- 读写禁止重排序;
- 保证了初始化安全;
- 只要对象时正确构造的,那么不需要使用同步来保证任意线程都能看到这个final域在构造函数中被初始化之后的数据;