synchronized和volatile关键字详解

synchronized

一、synchronized可作用范围

两种表现形式(方法层面和代码块层面)。两种作用范围(对象锁和类锁)

	1public synchronized void demo1(){}
	2public static synchronized void demo2(){}
	3public void demo3(){
		synchronized(this){}
	}
	
	4public void demo3(){
		Demo demo = new Demo();
		synchronized(demo){}
	}
	5public 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规则)去禁止重排序
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值