前言:前一篇文章主要描述了多線程中訪成員變量與局部變量問題,咱們知道訪成員變量有線程安全問題,在多線程程序中java
咱們能夠經過使用synchronized關鍵字完成線程的同步,可以解決部分線程安全問題安全
在java中synchronized同步關鍵字可使用在靜態方法和實例方法中使用,二者的區別在於:多線程
對象鎖與類鎖
對象鎖
當一個對象中有synchronized method或synchronized block的時候調用此對象的同步方法或進入其同步區域時,就必須先得到對象鎖。併發
若是此對象的對象鎖已被其餘調用者佔用,則須要等待此鎖被釋放高併發
類鎖
由上述同步靜態方法引伸出一個概念,那就是類鎖。其實系統中並不存在什麼類鎖。當一個同步靜態方法被調用時,系統獲取的其實就是表明該類的類對象的對象鎖
在程序中獲取類鎖
能夠嘗試用如下方式獲取類鎖
synchronized (xxx.class) {...}
synchronized (Class.forName("xxx")) {...}
同時獲取2類鎖
同時獲取類鎖和對象鎖是容許的,並不會產生任何問題,但使用類鎖時必定要注意,一旦產生類鎖的嵌套獲取的話,就會產生死鎖,由於每一個class在內存中都只能生成一個Class實例對象。測試
同步靜態方法/靜態變量互斥體
因爲一個class不論被實例化多少次,其中的靜態方法和靜態變量在內存中都只由一份。因此,一旦一個靜態的方法被申明爲synchronized。此類全部的實例化對象在調用此方法,共用同一把鎖,咱們稱之爲類鎖。一旦一個靜態變量被做爲synchronized block的mutex。進入此同步區域時,都要先得到此靜態變量的對象鎖this
代碼spa
/** * 同步代碼塊與同步實例方法的互斥 * * @author cary */ public class TestSynchronized { /** * 同步代碼塊 */ public void testBlock() { synchronized (this) { int i = 5; while (i-- > 0) { System.out .println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } } /** * 非同步普通方法 */ public void testNormal() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } /** * 同步實例方法 */ public synchronized void testMethod() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } /** * 主方法分別調用三個方法 * * @param args */ public static void main(String[] args) { final TestSynchronized test = new TestSynchronized(); Thread test1 = new Thread(new Runnable() { public void run() { test.testBlock(); } }, "testBlock"); Thread test2 = new Thread(new Runnable() { public void run() { test.testMethod(); } }, "testMethod"); test1.start(); ; test2.start(); test.testNormal(); } }
執行結果線程
testBlock : 4
main : 4
testBlock : 3
main : 3
testBlock : 2
main : 2
testBlock : 1
main : 1
testBlock : 0
main : 0
testMethod : 4
testMethod : 3
testMethod : 2
testMethod : 1
testMethod : 0code
上述的代碼,第一個方法時用了同步代碼塊的方式進行同步,傳入的對象實例是this,代表是當前對象,
固然,若是須要同步其餘對象實例,也不可傳入其餘對象的實例;第二個方法是修飾方法的方式進行同步。
由於第一個同步代碼塊傳入的this,因此兩個同步代碼所須要得到的對象鎖都是同一個對象鎖,
下面main方法時分別開啓兩個線程,分別調用testBlock()和testMethod()方法,那麼兩個線程都須要得到該對象鎖,
另外一個線程必須等待。上面也給出了運行的結果能夠看到:直到testBlock()線程執行完畢,釋放掉鎖testMethod線程纔開始執行
(兩個線程沒有穿插執行,證實是互斥的)
對於普通方法
結果輸出是交替着進行輸出的,這是由於,某個線程獲得了對象鎖,可是另外一個線程仍是能夠訪問沒有進行同步的方法或者代碼。
進行了同步的方法(加鎖方法)和沒有進行同步的方法(普通方法)是互不影響的,一個線程進入了同步方法,獲得了對象鎖,
其餘線程仍是能夠訪問那些沒有同步的方法(普通方法)
結論:synchronized只是一個內置鎖的加鎖機制,當某個方法加上synchronized關鍵字後,就代表要得到該內置鎖才能執行,
並不能阻止其餘線程訪問不須要得到該內置鎖的方法
類鎖的修飾(靜態)方法和代碼塊:
/** * * 類鎖與靜態方法鎖 * * @author cary */ public class TestSynchronized2 { /** * 類鎖 */ public void testClassLock() { synchronized (TestSynchronized2.class) { int i = 5; while (i-- > 0) { System.out .println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } } public static synchronized void testStaticLock() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } /** * 普通方法 */ public void testNormal() { int i = 5; while (i-- > 0) { System.out.println("normal-" + Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } /** * 靜態方法 */ public void testStaticNormal() { int i = 5; while (i-- > 0) { System.out.println("static-" + Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } /** * 測試synchronized鎖的互斥效果 * * @param args */ public static void main(String[] args) { final TestSynchronized2 test = new TestSynchronized2(); Thread testClass = new Thread(new Runnable() { public void run() { test.testClassLock(); } }, "testClassLock"); Thread testStatic = new Thread(new Runnable() { public void run() { TestSynchronized2.testStaticLock(); } }, "testStaticLock"); /** * 線程1 */ testClass.start(); /** * 線程2 */ testStatic.start(); /** * 成員方法 */ test.testNormal(); /** * 靜態方法 */ TestSynchronized2.testStaticLock(); } }
執行結果
testClassLock : 4 normal-main : 4 normal-main : 3 testClassLock : 3 normal-main : 2 testClassLock : 2 testClassLock : 1 normal-main : 1 testClassLock : 0 normal-main : 0 testStaticLock : 4 testStaticLock : 3 testStaticLock : 2 testStaticLock : 1 testStaticLock : 0 main : 4 main : 3 main : 2 main : 1 main : 0
類鎖和靜態方法鎖線程是分前後執行的,沒有相互交叉,類鎖和靜態方法鎖是互斥的
其實,類鎖修飾方法和代碼塊的效果和對象鎖是同樣的,由於類鎖只是一個抽象出來的概念,
只是爲了區別靜態方法的特色,由於靜態方法是全部對象實例共用的,
因此對應着synchronized修飾的靜態方法的鎖也是惟一的,因此抽象出來個類鎖。
結論:類鎖和靜態方法鎖是互斥的
鎖靜態方法和普通方法
/** * 鎖普通方法和靜態方法。 * * @author cary */ public class TestSynchronized3 { /** * 鎖普通方法 */ public synchronized void testNormal() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } /** * 鎖靜態方法 */ public static synchronized void testStatic() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } /** * 普通方法和靜態方法 * * @param args */ public static void main(String[] args) { final TestSynchronized test = new TestSynchronized(); Thread test1 = new Thread(new Runnable() { public void run() { test.testNormal(); } }, "testNormal"); Thread test2 = new Thread(new Runnable() { public void run() { TestSynchronized3.testStatic(); } }, "testStatic"); /** * 啓動普通方法線程 */ test1.start(); /** * 啓動靜態方法線程 */ test2.start(); } }
執行結果
testNormal : 4 testStatic : 4 testNormal : 3 testStatic : 3 testNormal : 2 testStatic : 2 testStatic : 1 testNormal : 1 testNormal : 0 testStatic : 0
上面代碼synchronized同時修飾靜態方法和實例方法,可是運行結果是交替進行的,
這證實了類鎖和對象鎖是兩個不同的鎖,控制着不一樣的區域,它們是互不干擾的。
一樣,線程得到對象鎖的同時,也能夠得到該類鎖,即同時得到兩個鎖,這是容許的。
到這裏,對synchronized的用法已經有了必定的瞭解。這時有一個疑問,既然有了synchronized修飾方法的同步方式,
爲何還須要synchronized修飾同步代碼塊的方式呢?而這個問題也是synchronized的缺陷所在
synchronized的缺陷:當某個線程進入同步方法得到對象鎖,那麼其餘線程訪問這裏對象的同步方法時,
必須等待或者阻塞,這對高併發的系統是致命的,這很容易致使系統的崩潰。若是某個線程在同步方法裏面發生了死循環,
那麼它就永遠不會釋放這個對象鎖,那麼其餘線程就要永遠的等待。這是一個致命的問題。