• jdk源码剖析三:锁Synchronized



    一、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

  • 相关阅读:
    Mysql游标的简明写法
    Sublime Text 介绍、用法、插件等
    [LeetCode#13] Roman to Integer
    [LeetCode#50] Pow(x, n)
    [LeetCode#240] Search a 2D Matrix II
    [LeetCode#238]Product of Array Except Self
    [LeetCode#171]Excel Sheet Column Number
    [LeetCode#258]Add Digits
    [LeetCode#264]Ugly Number II
    [LeetCode#263]Factorial Trailing Zeroes
  • 原文地址:https://www.cnblogs.com/dennyzhangdd/p/6670307.html
Copyright © 2020-2023  润新知