synchronized
一、synchronized可作用范围
两种表现形式(方法层面和代码块层面)。两种作用范围(对象锁和类锁)
1、public synchronized void demo1(){}
2、public static synchronized void demo2(){}
3、public void demo3(){
synchronized(this){}
}
4、public void demo3(){
Demo demo = new Demo();
synchronized(demo){}
}
5、public void demo5(){
synchronized(Demo.class){}
}
2、5等价==>作用域是全局的
二、synchronized的锁升级
jdk1.6之后对synchronized做了优化
对象头中有 偏向锁(00)、轻量级锁(01)、重量级锁(10)
1、因为jdk发现 大多数情况是一个线程(t1)能够顺利执行完的情况,
所以这种情况对锁的优化最好是无锁状态。 偏向锁就是个无锁状态。
线程通过CAS 去设置对象锁的状态。
2、如果t1再执行过程中,t2想着占用锁。t2不应该获取成功,如果成功。
则有可能会产生数据不安全。这样t2将对象锁变为轻量级锁。
然后通过自旋的方式去获取对象锁。自旋过程中获取到锁,就
3、如果自旋一定次数后,仍然没有获取到锁, 也就是自旋膨胀后,
将对象变成重量级锁。t2进入block状态。
三、wait 和notify的原理以及join的原理。
当对象使用wait方法的时候,线程进入等待队列。 该线程进入阻塞状态。
当使用notify方法的时候,该线程从等待队列进入到锁池。
然后进入锁的竞争。
join:保证线程的执行顺序。
调用join方法的时候相当于阻塞主线程,所以后面的逻辑
只能等到阻塞释放。当代码执行完后,会调用notifyAll方法
volatile
四、volatile(作用是:保证数据的可见性)
1、如果保证可见性的?
加上volatile关键字后,在该变量上多了一个lock的汇编指令。
2、什么是可见性?
a、硬件层面
CPU、内存、I/O设备(磁盘、固态硬盘)他们的计算速度是一次降低的。 I/O设备的计算速度远小于内存的计算速度。
因为在程序中执行的速度往往决定于运算比较慢的那个部分。 比如在读取文件的时候 cpu 用1ms, 内存用10ms ,磁盘用100ms。 执行完这块代码用的时间是 1 + 10 +100 = 111; 可以看出时间是取决于磁盘的。因次做出优化就是最大程度上利用CPU的资源。
优化:
(1)、CPU增加高速缓存
a:增加高速缓存后,有可能出现数据非安全。有图解决方法
1:主线锁
2:缓存锁
实现缓存锁是基于:缓存一致协议(MESI协议、)
MESI协议详解:
1、S (Shared) cpu0 和 cup1的数据和内存中的数据一致。 表示该数据为共享状态
2、M(Modify)和I(Invalid) cpu0和cpu1的值不一致,其中有一个状态为修改状态,一个为失效状态。例如cpu0将i赋值为2,
则cpu0的i为M状态,cpu1的i为I状态
3、E(Exclusive) 当出现M和I状态的时候,cpu1将I状态的i从缓存中清除,cpu0中的i从M状态变成了E状态。
虽然缓存一致性通过MESI协议解决,但是还不能解决数据可见性的问题,为什么?
void cpu0(){ value = 10; isfinshed = true;} void cpu1(){if((isfinshed) assert value == 10;}
在cpu1执行时候,有可能会出现false的情况。 因为 在cpu0中 value = 10;经过了从 S状态到M状态,这个修改需要通知
cpu1,然后cpu1将value修改为I状态,然后cpu1返回cpu0修改成功的消息。 然后cpu0中的value变为E状态。这个过程是
需要耗时的,也就相当于value =10;被阻塞到了。为了优化阻塞,cpu增加了storebuffer。 但是 isfinshed 再cpu0中为E状态,是可以直接修改的。 所以有可能出现 再cpu1中 value再阻塞过程中被cpu1使用
所以再硬件层面无法解决数据一致性的问题。。。但是cpu提供了指令(也就是通常说的内存屏障)
b、内存屏障
cpu提供了三种屏障读屏障、写屏障、全屏障
1、写屏障
再增加一个写屏障的时候,会告诉处理器已经存在storebuff里的指令要同步到主内存。
2、读屏障
增加了读屏障指令的时候,就会读取到最新的数据
3、全屏障
读屏障和写屏障配合使用就是全屏障
(2)、引入进程、线程的概念
通过如果线程阻塞。则切换cpu的时间片,这样能够充分的利用cpu
(3)、指令优化
体现在:重排序
b、JMM层面
因为cpu硬件上无法左到数据一致性,但是cpu提供了内存屏障指令。因此,再软件层面,告诉cpu什么时候使用内存屏障保证数据的一致性。
volatile 就是java代码层告诉硬件的要保证数据一致性的关键字。 当对象被volatile修饰的时候,再编译的时候就多出一个lock执行。
这就是开启cpu内存屏障的指令
导致可见性一致问题的根本原因是:高速缓存和重排序
数据一致性为题可以通过cpu的内存屏障来保证。但是指令重排序的问题,JMM怎么保证呢?
源代码-->编译器的重排序-->cpu层面的重排序(指令级、内存)-->最终的执行指令
1、什么叫重排序?
int a =1; int b = 2;这样再运行的时候不管先执行b还是先执行a,都不会影响到最终的结果。
但是以下这种情况是绝对不能进行重排序的:
int a = 0; int b = a;
int b = 0; a = b; b =1;
以上如果重排序的话 会影响到数据的最终的结果。 因此这样的情况肯定是允许重排序的。
再编译器层面增加了内存屏障:storestore(); loadload();storeload();
2、happends-before
A happends-before B 表示 A的代码执行一定再B之前。
六大原则:
1、程序的执行规则
2、volatile规则
3、传递性规则
4、start规则(主线程一定再线程调用start之前执行)
static int i =0;
public void static main(String[] args){
Thread t1 = new Thread(() ->{
//获取i的值
System.out.println(i); //这个值一定是10
});
i=10;
t.start();
}
5、join规则(join再start之前执行)
static int i =0;
public void static main(String[] args){
Thread t1 = new Thread(() ->{
i=10;
//获取i的值
System.out.println(i); //这个值一定是10
});
t.start();
t.join();
}
6、synchronized规则
综上:再JMM层面,提供了volatile、synchronized、final、(happends-before规则)去禁止重排序