一、Synchronized的基本使用
Synchronized是Java中解決並發問題的一種最常用的方法,也是最簡單的一種方法。 Synchronized的功能主要有三:(1)確保執行緒互斥的存取同步程式碼(2)確保共享變數的修改能夠及時可見(3)有效解決重排序問題。從語法上講,Synchronized總共有三種用法:
(1)修飾普通方法
(2)修飾靜態方法
(3)修飾代碼塊🎀
〜接下來幾個例子三種使用方式(為了方便比較,三段程式碼除了Synchronized的使用方式不同以外,其他基本上保持一致)。 1、沒有同步的情況:程式碼段一:package com.paddx.test.concurrent; public class SynchronizedTest { 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 SynchronizedTest test = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test.method2(); } }).start(); } }
Method 1 execute
Method 2 start
Method 2 execute
Method 2 end
Method 1 end
Method 1 execute
Method 1 endMethod 2 start
Method 2 executeMethod 2 end
3、靜態方法(類)同步化靜態方法的同步本質上是對類別的同步(靜態方法本質上是屬於類別的方法,而不是對像上的方法),所以即使test和test2屬於不同的對象,但是它們都屬於SynchronizedTest類別的實例,所以也只能順序的執行method1和method2,不能並發執行。
Method 1 start
Method 1 execute
Method 2 start
Method 2 executeMethod 2 end
rr
4、代碼塊同步Method 2 end
線程2都進入了對應的方法開始執行,但是線程2在進入同步區塊之前,需要等待線程1中同步區塊執行完成。
Method 1 start
Method 1 execute
Method 2 start
Method 2 execute
Method 2 end、Synchronized 問題還是急先來了解Synchronized的原理,再回頭上面的問題就一目了然了。我們先透過反編譯下面的程式碼來看看Synchronized是如何實現對程式碼區塊進行同步的:
package com.paddx.test.concurrent; public class SynchronizedTest { 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 SynchronizedTest test = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test.method2(); } }).start(); } }
反編譯結果:
關於這兩條指令的作用,我們直接參考JVM規範中描述:
package com.paddx.test.concurrent; public class SynchronizedTest { 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 SynchronizedTest test = new SynchronizedTest(); final SynchronizedTest test2 = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test2.method2(); } }).start(); } }
每個物件都有監視器鎖定(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試取得monitor的所有權,過程如下:
1、如果monitor的進入數為0,則該線程進入monitor,然後將進入數設為1 ,該線程即為monitor的所有者。
2、如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1.
3.如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0 ,再重新嘗試取得monitor的所有權。
monitorexit:
package com.paddx.test.concurrent; public class SynchronizedTest { 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 SynchronizedTest test = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test.method2(); } }).start(); } }
這段話的大概意思是:
執行monitorexit的執行緒必須是objectref所對應的monitor的擁有者。
指令執行時,monitor的進入數減1,如果減1後進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去取得這個 monitor 的所有權。
透過這兩段描述,我們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是透過一個monitor的物件來完成,其實wait/notify等方法也依賴於monitor對象,這就是為什麼只有monitor在同步的區塊或方法中才能呼叫wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。
我們再來看看同步方法的反編譯結果:
原始碼:
package com.paddx.test.concurrent; public class SynchronizedMethod { public synchronized void method() { System.out.println("Hello World!"); } }
从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
三、运行结果解释
有了对Synchronized原理的认识,再来看上面的程序就可以迎刃而解了。
1、代码段2结果:
虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。
2、代码段3结果:
虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。
3、代码段4结果:
对于代码块的同步实质上需要获取Synchronized关键字后面括号中对象的monitor,由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。
四 总结
Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用Synchronized关键字,另一方面也能够帮助我们更好的理解并发编程机制,有助我们在不同的情况下选择更优的并发策略来完成任务。对平时遇到的各种并发问题,也能够从容的应对。
更多Java 并发编程学习笔记之Synchronized简介相关文章请关注PHP中文网!