线程安全
来源:互联网 发布:奔驰诊断软件下载 编辑:程序博客网 时间: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)修饰普通方法
(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
/** * @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(); }}
Method 1 startMethod 1 executeMethod 1 endMethod 2 startMethod 2 executeMethod 2 end
/** * @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(); }}
Method 1 startMethod 1 executeMethod 1 endMethod 2 startMethod 2 executeMethod 2 end
/** * @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(); }}
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.
• 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:
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
阅读全文
1 0
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- 线程安全
- Solr之单机安装-yellowcong
- mysql基于amoeba的读写分离
- hashCode相关性能优化
- Jzoj3542 冒泡排序
- 文章标题
- 线程安全
- LeetCode.537 Complex Number Multiplication
- HDOJ 2550 百步穿杨
- DP
- [Leetcode从零开刷]226. Invert Binary Tree
- Virtual Box虚拟机文件瘦身处理
- Windows系统以及Mac系统/Linux系统配置环境变量
- Ansible中自定义变量的使用
- openssl 根据证书生成p7b证书链