只会一点java

java持续学习者,每月一篇博客。罗列出技术栈,慢慢完善,持续学习,总有一天,你会追上甚至超越曾经的大神。
  博客园  :: 首页  :: 联系 :: 订阅 订阅  :: 管理

jdk源码剖析三:锁Synchronized

Posted on 2017-04-05 19:53  只会一点java  阅读(4442)  评论(0编辑  收藏  举报

一、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且一直在优化,到目前JDK8依然广泛使用。本文从功能、常见用法、源码实现三方面彻底剖析了synchronized锁。相信读者会有所收获。

 

 ========附言分割线=====

附:全文参考http://www.cnblogs.com/paddix/p/5367116.html