【轉】使用synchronized進行Java線程同步
線程同步指多個線程同時訪問某資源時,采用一系列的機制以保證同時最多只能一個線程訪問該資源。為什么需要線程同步呢?
我們舉一個最簡單的例子來說明為什么需要線程同步。
比如有一本書(有且只有一本),交給多個售貨員同時去賣;
如果其中任何一個售貨員把這本書給賣了,其他售貨員就不能再賣這本書了。
現實生活中,如果要保證該書不會被多個售貨員同時賣掉,必須要有一種機制來保證:
比如,售貨員應該拿到該書之后才能開始賣書,暫時拿不到的話就只能等該書被退回柜臺。
售書的完整的例子可以參考 范例解說Java里的線程概念與線程同步技術 一文
這里,每一個售貨員售書可以看作一個線程。欲售的書便是各線程需要共享的資源。
開始售書之前,需要取得該書(資源),取不到情況下等待:資源取得
開始售書之后,則需要取得對該書的獨享控制(不讓他人拿到該書):資源加鎖
售完書時,需要通知柜臺該書已售出;或者未售出時,把書退回柜臺(通知他人可以拿到該書):資源解鎖
synchronized控制線程同步的概念跟此完全一樣。
Java里可以使用synchronized來同步代碼塊或者方法。
同步代碼塊例:
- synchronized(欲同步的對象obj) {
- 需要同步的代碼塊
- }
可以同步代碼塊。
synchronized (obj) 表示若多個線程同時訪問時,只讓其中一個線程最先取得obj對象并對其加鎖,其它線程則阻塞直到取得obj對象的線程執行完代碼塊,此時被加鎖的obj對象得到釋放(解鎖),其它線程得到通知取得該book對象繼續執行。
很多情況下,可以使用synchronized (this){...}來同步代碼塊。但需要注意的是,使用this作為同步對象的話,如果同一個類中存在多個synchronized (this){...}代碼塊,其中任何一個synchronized(this)代碼塊處于被執行狀態,則其它線程對其他synchronized(this)代碼塊的訪問也會受到阻塞。
為了說明這個問題,我們舉例說明:

- publicclass HelloSynchronized {
- publicstaticvoid main(String[] args) {
- //
- HelloSynchronized helloSynchronized = new HelloSynchronized();
- //創建2個線程t1, t2,分別調用HelloSynchronized helloSynchronized的2個方法method1,與method2
- Thread t1 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method1"), "t1");
- Thread t2 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method2"), "t2");
- t1.start();
- t2.start();
- }
- //synchronized public void method1() { //同步方法
- publicvoid method1() {
- synchronized (this) { //同步塊
- System.out.println(Thread.currentThread().getName()
- + " enter method1");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- // do nothing
- }
- System.out.println(Thread.currentThread().getName()
- + " exit method1");
- }
- }
- //synchronized public void method2() { //同步方法
- publicvoid method2() {
- synchronized (this) { //同步塊
- System.out.println(Thread.currentThread().getName()
- + " enter method2");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- // do nothing
- }
- System.out.println(Thread.currentThread().getName()
- + " exit method2");
- }
- }
- }
- class HelloSynchronizedRunnalbe implements Runnable {
- private HelloSynchronized helloSynchronized;
- private String methodName;
- public HelloSynchronizedRunnalbe(HelloSynchronized helloSynchronized, String methodName) {
- this.helloSynchronized = helloSynchronized;
- this.methodName = methodName;
- }
- publicvoid run() {
- if (methodName.equals("method1")) {
- helloSynchronized.method1();
- } elseif (methodName.equals("method2")) {
- helloSynchronized.method2();
- }
- }
- }
運行結果為:
t1 enter method1
t1 exit method1
t2 enter method2
t2 exit method2
等到線程t1結束后,t2才開始運行(t2受到阻塞)t1 exit method1
t2 enter method2
t2 exit method2
再把synchronized (this)去掉,運行結果為:
t1 enter method1
t2 enter method2
t1 exit method1
t2 exit method2
線程t1,t2同時運行t2 enter method2
t1 exit method1
t2 exit method2
同步方法例:
- synchronizedprivatevoid sellBook(Book book) {
- ...
- }
這種方法其實相當于
- privatevoid sellBook(Book book) {
- synchronized(this) {
- ...
- }
- }
由于默認采用this作為同步對象,所以當一個類中有多個synchronized方法時,同樣會存在以上問題:即如果有一個線程訪問其中某個synchronized方法時,直到該方法執行完畢,其它線程對其它synchronized方法的訪問也將受到阻塞。
大家可以把上面的例子稍加改造,去掉代碼中的synchronized (this),改為synchronized public void method1(),synchronized public void method2()同步形式,運行后會得到同樣結果。
多同步代碼塊synchronized(this){...}的多線程阻塞問題(包括synchronized同步方法),在并發處理的系統中(比如WEB服務器)會嚴重影響性能,建議慎重使用。可以使用synchronized(obj){...}縮小同步資源對象的范圍來解決這個問題。