一、Synchronized作用
(1)确保线程互斥的访问同步代码
(2)保证共享变量的修改能够及时可见
(3)有效解决重排序问题。(Synchronized同步中的代码JVM不会轻易优化重排序)
二、Synchronized常见用法分析
1.修饰普通方法
1 package lock; 2 3 /** 4 * 5 * @ClassName:SynchronizedDemo 6 * @Description:测试synchronized 7 * @author diandian.zhang 8 * @date 2017年4月1日下午7:02:34 9 */ 10 public class SynchronizedDemo { 11 12 public synchronized void method1(){ 13 System.out.println("进入方法1"); 14 try { 15 System.out.println("方法1执行"); 16 Thread.sleep(3000); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 System.out.println("方法1 end"); 21 } 22 23 public synchronized void method2(){ 24 System.out.println("进入方法2"); 25 try { 26 System.out.println("方法2执行"); 27 Thread.sleep(000); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 System.out.println("方法2 end"); 32 } 33 34 public static void main(String[] args) { 35 SynchronizedDemo demo = new SynchronizedDemo(); 36 new Thread(new Runnable() { 37 @Override 38 public void run() { 39 demo.method1(); 40 } 41 }).start(); 42 43 new Thread(new Runnable() { 44 @Override 45 public void run() { 46 demo.method2(); 47 } 48 }).start(); 49 } 50 }
结果:
1 进入方法1 2 方法1执行 3 方法1 end 4 进入方法2 5 方法2执行 6 方法2 end
可见:修饰普通方法,线程2需要等待线程1的method1执行完成才能开始执行method2方法,方法级别串行执行。
2.修饰静态方法
1 package lock; 2 3 /** 4 * 5 * @ClassName:SynchronizedDemo2 6 * @Description:修饰静态方法 7 * @author diandian.zhang 8 * @date 2017年4月5日上午10:48:56 9 */ 10 public class SynchronizedDemo2 { 11 12 public static synchronized void method1(){ 13 System.out.println("进入方法1"); 14 try { 15 System.out.println("方法1执行"); 16 Thread.sleep(3000); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 System.out.println("方法1 end"); 21 } 22 23 public static synchronized void method2(){ 24 System.out.println("进入方法2"); 25 try { 26 System.out.println("方法2执行"); 27 Thread.sleep(1000); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 System.out.println("方法2 end"); 32 } 33 34 public static void main(String[] args) { 35 new Thread(new Runnable() { 36 @Override 37 public void run() { 38 SynchronizedDemo2.method1(); 39 } 40 }).start(); 41 42 new Thread(new Runnable() { 43 @Override 44 public void run() { 45 SynchronizedDemo2.method2(); 46 } 47 }).start(); 48 } 49 }
运行结果:
1 进入方法1 2 方法1执行 3 进入方法2 4 方法1 end 5 方法2执行 6 方法2 end
可见:修饰静态方法,本质是对类的同步,任何实例调用方法,都类级别串行(每个实例不一定串行)执行。
3.修饰代码块
1 package lock; 2 3 /** 4 * 5 * @ClassName:SynchronizedDemo3 6 * @Description:Synchronized修饰代码块 7 * @author diandian.zhang 8 * @date 2017年4月5日上午10:49:50 9 */ 10 public class SynchronizedDemo3 { 11 12 public void method1(){ 13 System.out.println("进入方法1"); 14 try { 15 synchronized (this) { 16 System.out.println("方法1执行"); 17 Thread.sleep(3000); 18 } 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 System.out.println("方法1 end"); 23 } 24 25 public void method2(){ 26 System.out.println("进入方法2"); 27 try { 28 synchronized (this) { 29 System.out.println("方法2执行"); 30 Thread.sleep(1000); 31 } 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 System.out.println("方法2 end"); 36 } 37 38 public static void main(String[] args) { 39 SynchronizedDemo3 demo = new SynchronizedDemo3(); 40 new Thread(new Runnable() { 41 @Override 42 public void run() { 43 demo.method1(); 44 } 45 }).start(); 46 47 new Thread(new Runnable() { 48 @Override 49 public void run() { 50 demo.method2(); 51 } 52 }).start(); 53 } 54 }
运行结果:
1 进入方法1 2 方法1执行 3 进入方法2 4 方法1 end 5 方法2执行 6 方法2 end
可见,修饰代码块,只锁住代码块的执行顺序。代码块级别串行。(例如上面的方法1和方法2没能串行,因为锁住的是同一个对象,但是同步代码块只包住了方法中的一部分)
三、Synchronized 原理
实际上,JVM只区分两种不同用法 1.修饰代码块 2.修饰方法。什么,你不信?好吧,上SE8规范:http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.14
上图中,红框框中说明了,1.monitorenter+monitorexit(上图中的onlyMe方法中同步代码块) 2.修饰方法
这一切都是规范说的,下面自测一下吧~
下面,我们通过JDK自带工具反编译,查看包含java字节码的指令。
3.1 synchronized修饰代码块
java源码如下:
1 public class SynchronizedDemo { 2 public void method (){ 3 synchronized (this) { 4 System.out.println("method 1 start!!!!"); 5 } 6 } 7 }
javac -encoding utf-8 SynchronizedDemo.java 编译生成class 后,javap -c 反编译一下,看指令:
这里着重分析2个monitorenter、monitorexit这两个指令。这里以JSE8位为准,查到属于JVM指令集。官网各种API、JVM规范,指令等,传送门:http://docs.oracle.com/javase/8/docs/。
1.monitorenter监视器准入指令
主要看上图中Description:
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1.如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2.如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.’如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
2.monitorexit监视器释放指令
主要看上图中Description:
1.执行monitorexit的线程必须是objectref所对应的monitor的所有者。
2.指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
3.2 synchronized修饰方法
java源码如下:
1 package lock; 2 3 /** 4 * 5 * @ClassName:SynchronizedDemo0 6 * @Description: Synchronized修饰方法 7 * @author diandian.zhang 8 * @date 2017年4月5日下午6:18:12 9 */ 10 public class SynchronizedDemo0 { 11 public synchronized void method (){ 12 System.out.println("method start!!!!"); 13 } 14 }
javap -v 查看class文件,发现method上加了同步标签,本质上还是monitor
3.3 synchronized终极原理C++实现
jdk源码剖析二: 对象内存布局、synchronized终极原理
四、总结 JDK一直没有放弃synchronized且一直在优化,到目前JDK8依然广泛使用。本文从功能、常见用法、源码实现三方面彻底剖析了synchronized锁。相信读者会有所收获。
========附言分割线=====
附:全文参考http://www.cnblogs.com/paddix/p/5367116.html