线程安全

来源:互联网 发布:奔驰诊断软件下载 编辑:程序博客网 时间:2024/06/14 15:50

为什么要线程安全?

    无论何时,只要有多于一个的线程访问给定的状态变量,并且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问。否则,就有可能出现写入线程未把更新写入共享内存,而读取线程读取旧值的情况,从而造成线程不安全的情况。Java中首要的同步机制是synchronized关键字。他提供了独占锁。除此以外,属于同步还包括volatile关键字、显示锁和原理变量。

    在没有正确同步的情况下,如果多个线程访问同一个变量,我们的程序就存在隐患。解决此隐患的方法:

    ·不跨线程共享变量

    ·控制状态变量为不可变

    ·在任何访问状态变量的时候使用同步

什么是线程安全?

    线程安全的定义,其关键在于“正确性”。
    一个线程安全的类,是指类在被多个线程访问时,类可以持续进行正确的行为。
    当多个线程访问同一个类时,如果不需要考虑这些线程在运行时的调度和交替执行,并且不需要额外的同步以及在调用代码时不需要做其他的同步,这个类的行为就可以保持正确的,那么这个类就是线程安全的。

无状态的servlet是线程安全的

    无对象的状态永远是线程安全的。
/** * 无状态servlet * @author 落叶飞翔的蜗牛 * @date 2017年12月2日 下午8:03:24 */public class MyServlet extends HttpServlet {private static final long serialVersionUID = 5851407415190805590L;@Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        System.out.println(">>>>>>>>>>MyServlet.doGet()<<<<<<<<<<<");        doPost(req, resp);    }@Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        System.out.println(">>>>>>>>>>doPost()<<<<<<<<<<<");        resp.setContentType("text/html");          PrintWriter out = resp.getWriter();          out.println("<html>");          out.println("<head>");          out.println("<title>Hello World</title>");          out.println("</head>");          out.println("<body>");          out.println("<h1>MyServlet.doPost</h1>");          out.println("</body>");          out.println("</html>");     }}

原子性

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。比如 int a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。在比如:++a操作,就不具有原子性。其实++a是一个复合操作。这个复合操作包含:(1)获取当前a的值  (2)a的值加1  (3)写入新的值。这是一个“读-写-改”操作(read-modify-write)

竞争条件

       当程序的正确性依赖于运行中相关的时序或者多线程的交替,就产生了竞争条件。最常见的竞争条件是“检查再运行”(check-then-act)——使用一个潜在的过期值作为决定下一步操作的前提。
       检查再运行:你观察到一些事情为真,然后基于你的观察去执行一些动作;但事实上,从观察到执行的这段时间内,观察结果可能已经无效了,从而引发错误。

 惰性初始化中的竞争条件

        以下是单例模式的懒汉模式的一种实现:
/** * 惰性初始化中存在竞争条件(不要这么做) * @author 落叶飞翔的蜗牛 * @date 2017年12月2日 下午10:26:35 */public class LazyInitialRaceDemo {LazyInitialRaceDemo lazyInitialRaceDemo = null;private LazyInitialRaceDemo() {}public LazyInitialRaceDemo getInstance() {if (lazyInitialRaceDemo == null) {lazyInitialRaceDemo = new LazyInitialRaceDemo();}return lazyInitialRaceDemo;}}
       LazyInitialRaceDemo中的竞争条件会破坏其正确性。假设A、B两个线程执行getInstance,A看到instance为null,并实例化一个LazyInitialRaceDemo。同时,B线程也在检查instance是否为null,此时instance是否为null,这时是依赖于时序的,是无法预期的。它包括调度的无偿性,以及A线程初始化并设置lazyInitialRaceDemo的耗时。如果B检查到的lazyInitialRaceDemo也为null,那么两个线程调用getInstance就会得到不同的结果。而我们期望的是getInstance得到相同的实例。

内部锁

       Java提供了内部锁机制:synchronized。synchronized方法的锁,就是方法所在对象本身。静态synchronized方法是从Class对象上获取锁。
       Java中每个对象都扮演一个用于同步的锁的角色,这些内置锁被称为内部锁(intrinsic locks)或者监视器锁(monitor locks)。执行线程进入synchronized块之前自动获取锁,而无论是正常退出synchronized还是抛出异常,线程都会放弃对synchronized块的控制时,自动释放锁。获取内部锁的唯一途径:进入内部锁保护的同步块或者方法。

/** * 无状态servlet * @author 落叶飞翔的蜗牛 * @date 2017年12月2日 下午8:03:24 */public class MyServlet extends HttpServlet {private static final long serialVersionUID = 5851407415190805590L;/** * 调用次数 */private Long callCount;@Override    protected synchronized void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        System.out.println(">>>>>>>>>>MyServlet.doGet()<<<<<<<<<<<");        doPost(req, resp);    }/** * 增加synchronized,同一时间只有一个线程可以进入doGet */@Override    protected synchronized void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        System.out.println(">>>>>>>>>>doPost()<<<<<<<<<<<");        callCount++;        resp.setContentType("text/html");          PrintWriter out = resp.getWriter();          out.println("<html>");          out.println("<head>");          out.println("<title>Hello World</title>");          out.println("</head>");          out.println("<body>");          out.println("<h1>MyServlet.doPost</h1>");          out.println("</body>");          out.println("</html>");     }}

Synchronized 原理

从语法上讲,Synchronized总共有三种用法:

  (1)修饰普通方法

  (2)修饰静态方法

  (3)修饰代码块

  接下来我就通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了Synchronized的使用方式不同以外,其他基本保持一致)。

1、没有同步的情况:
/**  * @Title: NoSyncTest.java  * @Package sync.nosync  * @Description: 没有同步的代码  * @author 落叶飞翔的蜗牛  * @date 2017年12月3日  * @version V1.0  */  package sync.nosync;/** * @author 落叶飞翔的蜗牛 * @date 2017年12月3日 上午10:20:31 */public class NoSyncTest {    public void method1(){        System.out.println("Method 1 start");        try {            System.out.println("Method 1 execute");            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 1 end");    }    public void method2(){        System.out.println("Method 2 start");        try {            System.out.println("Method 2 execute");            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 2 end");    }    public static void main(String[] args) {        final NoSyncTest test = new NoSyncTest();        new Thread(new Runnable() {            @Override            public void run() {                test.method1();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                test.method2();            }        }).start();    }}

执行结果如下,线程1和线程2同时进入执行状态,线程2执行速度比线程1快,所以线程2先执行完成,这个过程中线程1和线程2是同时执行的。

Method 1 startMethod 1 executeMethod 2 startMethod 2 executeMethod 2 endMethod 1 end

2、对普通方法同步:
/**  * @Title: MethodSyncTest.java  * @Package sync.methodsync  * @Description: 方法级别同步* @author 落叶飞翔的蜗牛  * @date 2017年12月3日 上午10:24:40* @version V1.0  */  package sync.methodsync;/** * @author 落叶飞翔的蜗牛 * @date 2017年12月3日 上午10:24:40 */public class MethodSyncTest {    public synchronized void method1(){        System.out.println("Method 1 start");        try {            System.out.println("Method 1 execute");            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 1 end");    }    public synchronized void method2(){        System.out.println("Method 2 start");        try {            System.out.println("Method 2 execute");            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 2 end");    }    public static void main(String[] args) {        final MethodSyncTest test = new MethodSyncTest();        new Thread(new Runnable() {            @Override            public void run() {                test.method1();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                test.method2();            }        }).start();    }}

执行结果如下,可以很明显的看出,线程2需要等待线程1的method1执行完成才能开始执行method2方法。

Method 1 startMethod 1 executeMethod 1 endMethod 2 startMethod 2 executeMethod 2 end

3、静态方法(类)同步
/**  * @Title: StaticMethodSyncTest.java  * @Package sync.staticmethod  * @Description: TODO  * @author 落叶飞翔的蜗牛  * @date 2017年12月3日 上午10:28:43* @version V1.0  */  package sync.staticmethod;/** * @author 落叶飞翔的蜗牛 * @date 2017年12月3日 上午10:28:43 */public class StaticMethodSyncTest {    public static synchronized void method1(){        System.out.println("Method 1 start");        try {            System.out.println("Method 1 execute");            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 1 end");    }    public static synchronized void method2(){        System.out.println("Method 2 start");        try {            System.out.println("Method 2 execute");            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 2 end");    }    public static void main(String[] args) {        final StaticMethodSyncTest test = new StaticMethodSyncTest();        final StaticMethodSyncTest test2 = new StaticMethodSyncTest();        new Thread(new Runnable() {            @Override            public void run() {                test.method1();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                test2.method2();            }        }).start();    }}

执行结果如下,对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使test和test2属于不同的对象,但是它们都属于SynchronizedTest类的实例,所以也只能顺序的执行method1和method2,不能并发执行。

Method 1 startMethod 1 executeMethod 1 endMethod 2 startMethod 2 executeMethod 2 end

4、代码块同步
/**  * @Title: CodeBlockSyncTest.java  * @Package sync.codeblocksync  * @Description: TODO  * @author 落叶飞翔的蜗牛  * @date 2017年12月3日 上午10:31:46* @version V1.0  */  package sync.codeblocksync;/** * @author 落叶飞翔的蜗牛 * @date 2017年12月3日 上午10:31:46 */public class CodeBlockSyncTest {    public void method1(){        System.out.println("Method 1 start");        try {            synchronized (this) {                System.out.println("Method 1 execute");                Thread.sleep(3000);            }        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 1 end");    }    public void method2(){        System.out.println("Method 2 start");        try {            synchronized (this) {                System.out.println("Method 2 execute");                Thread.sleep(1000);            }        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 2 end");    }    public static void main(String[] args) {        final CodeBlockSyncTest test = new CodeBlockSyncTest();        new Thread(new Runnable() {            @Override            public void run() {                test.method1();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                test.method2();            }        }).start();    }}

执行结果如下,虽然线程1和线程2都进入了对应的方法开始执行,但是线程2在进入同步块之前,需要等待线程1中同步块执行完成。

Method 1 startMethod 1 executeMethod 2 startMethod 1 endMethod 2 executeMethod 2 end

synchronized原理需要理解monitorenter、monitorexit概念
monitorenter:
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

大概意思是:
每个对象都有一个与其关联的监视器。当监视器被占有的时候,监视器就处于锁定状态。线程执行moniterenter尝试获得监视器的所有权。
·如果监视器的进入次数为0,线程进入监视器,并将监视器的进入次数置为1次。接下来此线程就是这个监视器的所有者。
·如果线程已经拥有了监视器的所有权,当线程重现进入监视器,监视器的进入次数加1。
·如果其他线程已经拥有了监视器的所有权,那么线程将会阻塞,直到监视器的进入次数减到0时,线程才会再次常识获取监视器的所有权。

monitorexit:
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

大概意思是:
执行monitorexit的线程必定是与对象实例相关联的监视器的所有者。
线程退出监视器的时候,减少一次监视器的进入次数。如果监视器的进入次数减少到0,表示监视器目前没有所有者,这时候其他阻塞的线程就可以尝试获取监视器的所有权。

下面先来看看CodeBlockSyncTest反编译的结果:
Compiled from "CodeBlockSyncTest.java"public class sync.codeblocksync.CodeBlockSyncTest {  public sync.codeblocksync.CodeBlockSyncTest();    Code:       0: aload_0       1: invokespecial #8                  // Method java/lang/Object."<init>":()V       4: return  public void method1();    Code:       0: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;       3: ldc           #21                 // String Method 1 start       5: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V       8: aload_0       9: dup      10: astore_1      11: monitorenter      12: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;      15: ldc           #29                 // String Method 1 execute      17: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V      20: ldc2_w        #31                 // long 3000l      23: invokestatic  #33                 // Method java/lang/Thread.sleep:(J)V      26: aload_1      27: monitorexit      28: goto          39      31: aload_1      32: monitorexit      33: athrow      34: astore_1      35: aload_1      36: invokevirtual #39                 // Method java/lang/InterruptedException.printStackTrace:()V      39: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;      42: ldc           #44                 // String Method 1 end      44: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V      47: return    Exception table:       from    to  target type          12    28    31   any          31    33    31   any           8    34    34   Class java/lang/InterruptedException  public void method2();    Code:       0: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;       3: ldc           #52                 // String Method 2 start       5: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V       8: aload_0       9: dup      10: astore_1      11: monitorenter      12: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;      15: ldc           #54                 // String Method 2 execute      17: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V      20: ldc2_w        #56                 // long 1000l      23: invokestatic  #33                 // Method java/lang/Thread.sleep:(J)V      26: aload_1      27: monitorexit      28: goto          39      31: aload_1      32: monitorexit      33: athrow      34: astore_1      35: aload_1      36: invokevirtual #39                 // Method java/lang/InterruptedException.printStackTrace:()V      39: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;      42: ldc           #58                 // String Method 2 end      44: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V      47: return    Exception table:       from    to  target type          12    28    31   any          31    33    31   any           8    34    34   Class java/lang/InterruptedException  public static void main(java.lang.String[]);    Code:       0: new           #1                  // class sync/codeblocksync/CodeBlockSyncTest       3: dup       4: invokespecial #62                 // Method "<init>":()V       7: astore_1       8: new           #34                 // class java/lang/Thread      11: dup      12: new           #63                 // class sync/codeblocksync/CodeBlockSyncTest$1      15: dup      16: aload_1      17: invokespecial #65                 // Method sync/codeblocksync/CodeBlockSyncTest$1."<init>":(Lsync/codeblocksync/CodeBlockSyncTest;)V      20: invokespecial #68                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V      23: invokevirtual #71                 // Method java/lang/Thread.start:()V      26: new           #34                 // class java/lang/Thread      29: dup      30: new           #74                 // class sync/codeblocksync/CodeBlockSyncTest$2      33: dup      34: aload_1      35: invokespecial #76                 // Method sync/codeblocksync/CodeBlockSyncTest$2."<init>":(Lsync/codeblocksync/CodeBlockSyncTest;)V      38: invokespecial #68                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V      41: invokevirtual #71                 // Method java/lang/Thread.start:()V      44: return}


如上所示,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

我们再来看一下同步方法的反编译结果:
Classfile /E:/work/github_workspace/gs-spring-boot/complete/target/classes/sync/methodsync/MethodSyncTest.class  Last modified 2017-12-3; size 1480 bytes  MD5 checksum 401877655824104297a11918ae9ab3d7  Compiled from "MethodSyncTest.java"public class sync.methodsync.MethodSyncTest  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_SUPERConstant pool:   #1 = Class              #2             // sync/methodsync/MethodSyncTest   #2 = Utf8               sync/methodsync/MethodSyncTest   #3 = Class              #4             // java/lang/Object   #4 = Utf8               java/lang/Object   #5 = Utf8               <init>   #6 = Utf8               ()V   #7 = Utf8               Code   #8 = Methodref          #3.#9          // java/lang/Object."<init>":()V   #9 = NameAndType        #5:#6          // "<init>":()V  #10 = Utf8               LineNumberTable  #11 = Utf8               LocalVariableTable  #12 = Utf8               this  #13 = Utf8               Lsync/methodsync/MethodSyncTest;  #14 = Utf8               method1  #15 = Fieldref           #16.#18        // java/lang/System.out:Ljava/io/PrintStream;  #16 = Class              #17            // java/lang/System  #17 = Utf8               java/lang/System  #18 = NameAndType        #19:#20        // out:Ljava/io/PrintStream;  #19 = Utf8               out  #20 = Utf8               Ljava/io/PrintStream;  #21 = String             #22            // Method 1 start  #22 = Utf8               Method 1 start  #23 = Methodref          #24.#26        // java/io/PrintStream.println:(Ljava/lang/String;)V  #24 = Class              #25            // java/io/PrintStream  #25 = Utf8               java/io/PrintStream  #26 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V  #27 = Utf8               println  #28 = Utf8               (Ljava/lang/String;)V  #29 = String             #30            // Method 1 execute  #30 = Utf8               Method 1 execute  #31 = Long               3000l  #33 = Methodref          #34.#36        // java/lang/Thread.sleep:(J)V  #34 = Class              #35            // java/lang/Thread  #35 = Utf8               java/lang/Thread  #36 = NameAndType        #37:#38        // sleep:(J)V  #37 = Utf8               sleep  #38 = Utf8               (J)V  #39 = Methodref          #40.#42        // java/lang/InterruptedException.printStackTrace:()V  #40 = Class              #41            // java/lang/InterruptedException  #41 = Utf8               java/lang/InterruptedException  #42 = NameAndType        #43:#6         // printStackTrace:()V  #43 = Utf8               printStackTrace  #44 = String             #45            // Method 1 end  #45 = Utf8               Method 1 end  #46 = Utf8               e  #47 = Utf8               Ljava/lang/InterruptedException;  #48 = Utf8               StackMapTable  #49 = Utf8               method2  #50 = String             #51            // Method 2 start  #51 = Utf8               Method 2 start  #52 = String             #53            // Method 2 execute  #53 = Utf8               Method 2 execute  #54 = Long               1000l  #56 = String             #57            // Method 2 end  #57 = Utf8               Method 2 end  #58 = Utf8               main  #59 = Utf8               ([Ljava/lang/String;)V  #60 = Methodref          #1.#9          // sync/methodsync/MethodSyncTest."<init>":()V  #61 = Class              #62            // sync/methodsync/MethodSyncTest$1  #62 = Utf8               sync/methodsync/MethodSyncTest$1  #63 = Methodref          #61.#64        // sync/methodsync/MethodSyncTest$1."<init>":(Lsync/methodsync/MethodSyncTest;)V  #64 = NameAndType        #5:#65         // "<init>":(Lsync/methodsync/MethodSyncTest;)V  #65 = Utf8               (Lsync/methodsync/MethodSyncTest;)V  #66 = Methodref          #34.#67        // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V  #67 = NameAndType        #5:#68         // "<init>":(Ljava/lang/Runnable;)V  #68 = Utf8               (Ljava/lang/Runnable;)V  #69 = Methodref          #34.#70        // java/lang/Thread.start:()V  #70 = NameAndType        #71:#6         // start:()V  #71 = Utf8               start  #72 = Class              #73            // sync/methodsync/MethodSyncTest$2  #73 = Utf8               sync/methodsync/MethodSyncTest$2  #74 = Methodref          #72.#64        // sync/methodsync/MethodSyncTest$2."<init>":(Lsync/methodsync/MethodSyncTest;)V  #75 = Utf8               args  #76 = Utf8               [Ljava/lang/String;  #77 = Utf8               test  #78 = Utf8               SourceFile  #79 = Utf8               MethodSyncTest.java  #80 = Utf8               InnerClasses{  public sync.methodsync.MethodSyncTest();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #8                  // Method java/lang/Object."<init>":()V         4: return      LineNumberTable:        line 15: 0      LocalVariableTable:        Start  Length  Slot  Name   Signature            0       5     0  this   Lsync/methodsync/MethodSyncTest;  public synchronized void method1();    descriptor: ()V    flags: ACC_PUBLIC, ACC_SYNCHRONIZED    Code:      stack=2, locals=2, args_size=1         0: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;         3: ldc           #21                 // String Method 1 start         5: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V         8: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;        11: ldc           #29                 // String Method 1 execute        13: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V        16: ldc2_w        #31                 // long 3000l        19: invokestatic  #33                 // Method java/lang/Thread.sleep:(J)V        22: goto          30        25: astore_1        26: aload_1        27: invokevirtual #39                 // Method java/lang/InterruptedException.printStackTrace:()V        30: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;        33: ldc           #44                 // String Method 1 end        35: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V        38: return      Exception table:         from    to  target type             8    22    25   Class java/lang/InterruptedException      LineNumberTable:        line 18: 0        line 20: 8        line 21: 16        line 22: 22        line 23: 26        line 25: 30        line 26: 38      LocalVariableTable:        Start  Length  Slot  Name   Signature            0      39     0  this   Lsync/methodsync/MethodSyncTest;           26       4     1     e   Ljava/lang/InterruptedException;      StackMapTable: number_of_entries = 2        frame_type = 89 /* same_locals_1_stack_item */          stack = [ class java/lang/InterruptedException ]        frame_type = 4 /* same */  public synchronized void method2();    descriptor: ()V    flags: ACC_PUBLIC, ACC_SYNCHRONIZED    Code:      stack=2, locals=2, args_size=1         0: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;         3: ldc           #50                 // String Method 2 start         5: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V         8: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;        11: ldc           #52                 // String Method 2 execute        13: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V        16: ldc2_w        #54                 // long 1000l        19: invokestatic  #33                 // Method java/lang/Thread.sleep:(J)V        22: goto          30        25: astore_1        26: aload_1        27: invokevirtual #39                 // Method java/lang/InterruptedException.printStackTrace:()V        30: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;        33: ldc           #56                 // String Method 2 end        35: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V        38: return      Exception table:         from    to  target type             8    22    25   Class java/lang/InterruptedException      LineNumberTable:        line 29: 0        line 31: 8        line 32: 16        line 33: 22        line 34: 26        line 36: 30        line 37: 38      LocalVariableTable:        Start  Length  Slot  Name   Signature            0      39     0  this   Lsync/methodsync/MethodSyncTest;           26       4     1     e   Ljava/lang/InterruptedException;      StackMapTable: number_of_entries = 2        frame_type = 89 /* same_locals_1_stack_item */          stack = [ class java/lang/InterruptedException ]        frame_type = 4 /* same */  public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=5, locals=2, args_size=1         0: new           #1                  // class sync/methodsync/MethodSyncTest         3: dup         4: invokespecial #60                 // Method "<init>":()V         7: astore_1         8: new           #34                 // class java/lang/Thread        11: dup        12: new           #61                 // class sync/methodsync/MethodSyncTest$1        15: dup        16: aload_1        17: invokespecial #63                 // Method sync/methodsync/MethodSyncTest$1."<init>":(Lsync/methodsync/MethodSyncTest;)V        20: invokespecial #66                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V        23: invokevirtual #69                 // Method java/lang/Thread.start:()V        26: new           #34                 // class java/lang/Thread        29: dup        30: new           #72                 // class sync/methodsync/MethodSyncTest$2        33: dup        34: aload_1        35: invokespecial #74                 // Method sync/methodsync/MethodSyncTest$2."<init>":(Lsync/methodsync/MethodSyncTest;)V        38: invokespecial #66                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V        41: invokevirtual #69                 // Method java/lang/Thread.start:()V        44: return      LineNumberTable:        line 40: 0        line 42: 8        line 47: 23        line 49: 26        line 54: 41        line 55: 44      LocalVariableTable:        Start  Length  Slot  Name   Signature            0      45     0  args   [Ljava/lang/String;            8      37     1  test   Lsync/methodsync/MethodSyncTest;}SourceFile: "MethodSyncTest.java"InnerClasses:     #61; //class sync/methodsync/MethodSyncTest$1     #72; //class sync/methodsync/MethodSyncTest$2

        从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
        Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用Synchronized关键字,另一方面也能够帮助我们更好的理解并发编程机制,有助我们在不同的情况下选择更优的并发策略来完成任务。对平时遇到的各种并发问题,也能够从容的应对。

参考文献:
1.http://blog.csdn.net/gongpulin/article/details/51211616
2.https://www.cnblogs.com/paddix/p/5367116.html

原创粉丝点击