Java中synchronized關鍵字理解
好記性不如爛筆頭~~
並發編程中
synchronized
關鍵字的地位很重要,很多人都稱它為重量級鎖。利用
synchronized
實現同步的基礎:Java中每一個對象都可以作為鎖。具體表現為以下三種形式。
(1)對於普通同步方法,鎖是當前實例對象。
(2)對於靜態同步方法,鎖是當前類的Class對象。
(3)對於同步方法塊,鎖是synchronized括號裏配置的對象。
一、普通同步方法
使用synchronized關鍵字修飾一個普通方法,鎖住的是當前實例的對象。當synchronized鎖住該對象後,別的線程如果也想拿到這個對象的鎖,就必須等待這個線程執行完成釋放鎖,才能再次給對象加鎖,這樣才達到線程同步的目的。
實驗一
class Synch{ public synchronized void test1(){ System.out.println("test1開始"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("test1結束"); } public synchronized void test2(){ System.out.println("test2開始"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("test2結束"); } } public class SyncTest extends Synch{ public static void main(String args[]){ Synch s=new Synch(); Thread t1=new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub s.test1(); } }); Thread t2=new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub s.test2(); }}); t1.start(); t2.start(); } }
實驗結果:
分析上述代碼,類Synch中有兩個普通同步方法test1和test2.在主函數中實現了該類,同時定義兩個thread線程,run方法中分別調用類Synch的方法。synchronized實現的普通同步方法,鎖住的是當前實例對象,即Synch類對象。由於兩個線程是調用的同一個對象中的同步方法,所以只有一個線程釋放該對象的鎖,另一個線程才能調用。
實驗二
class Sync{ public synchronized void test(String threadname){ System.out.println(threadname+"開始"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(threadname+"結束"); } } class MyThread extends Thread{ public int i; public MyThread(int i){ this.i=i; } Sync s=new Sync(); public void run(){ //Sync sync=new Sync(); s.test("Thread"+i); } } public class SynTest { public static void main(String args[]){ for(int i=0;i<3;i++){ Thread thread=new MyThread(i); thread.start(); } } }
實驗結果:
上述代碼,每個線程中都new了一個Sync類的對象,也就是產生了三個Sync對象,由於不是同一個對象,所以可以多線程同時運行synchronized方法。
二、靜態同步方法
對於靜態同步方法,鎖是當前類的Class對象,所以,static synchronized方法也相當於全局鎖,相當於鎖住了代碼段。只有一個線程結束後,另一個線程才能獲得鎖。
實驗三
class Synch{ public static synchronized void test1(){ System.out.println("test1開始"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("test1結束"); } public static synchronized void test2(){ System.out.println("test2開始"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("test2結束"); } } public class SyncTest extends Synch{ public static void main(String args[]){ Synch s1=new Synch(); Synch s2=new Synch(); Thread t1=new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub s1.test1(); } }); Thread t2=new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub s2.test2(); }}); t1.start(); t2.start(); } }
實驗結果:
三、同步方法塊
這部分更好理解,鎖住的是synchronized括號裏配置的對象。
實驗四
public class Threadtest implements Runnable{ @Override public void run() { // TODO Auto-generated method stub synchronized(this){ for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } } public static void main(String[] args) { Threadtest t1 = new Threadtest(); Thread ta = new Thread(t1, "A"); Thread tb = new Thread(t1, "B"); ta.start(); tb.start(); } }
運行結果:
上述代碼,synchronized代碼塊括號裏配置的對象是this,同一個對象,所以只有一個線程訪問該代碼塊結束後,釋放鎖,另一個線程才能訪問該代碼塊。
需要註意的是,其他線程可以訪問非synchronized(this)同步代碼。如下代碼
實驗五
class MyThread1{ public void test1(){ synchronized(this){ System.out.println("同步代碼塊-test1開始"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("同步代碼塊-test1結束"); } } public void test2(){ try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("非同步代碼塊-test2"); } } public class ThreadTest1 { public static void main(String args[]){ MyThread1 t=new MyThread1(); Thread t1=new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub t.test1(); } }); Thread t2=new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub t.test2(); } }); t1.start(); t2.start(); } }
運行結果:
synchronized括號裏配置的是this,只是對這一段代碼進行了加鎖,同一個對象,只有一個線程能訪問該代碼塊,釋放鎖之後,其他線程才可以訪問,但是並不影響其他線程訪問非同步代碼塊。
同步代碼塊,同步方法的實現~~簡單理解
對於同步代碼塊是使用monitorenter、monitorexit指令實現的。每個對象都有一個監視器鎖(monitor),當monitor被占用時,就會處於鎖定狀態。
線程執行monitorenter指令嘗試獲取monitor的所有權。
- 如果monitor的進入數為0,則該線程進入monitor,並將進入數設置為1
- 如果該線程已經占有了該monitor,重新進入,進入數也要+1
- 如果其他線程占用了monitor,則該線程進入阻塞狀態,知道進入數為0
對於同步方法,常量池中多了ACC_SYNCHRONIZED標識符,方法調用時,先檢查該標識符的訪問標誌,如果設置了,則進程先獲取monitor,獲取成功後才能執行方法體,方法執行完之後再釋放monitor。
整理下,要不會忘記~~參考http://www.cnblogs.com/QQParadise/articles/5059824.html
Java中synchronized關鍵字理解