在java編程中,經常需要用到同步,而用得最多的也許是synchronized關鍵字了,下面看看這個關鍵字的用法。 因為synchronized關鍵字涉及到鎖的概念,所以先來了解一些相關的鎖知識。 java的內置鎖:每個java對象都可以用做一個實現同步的鎖,這些鎖成為內置鎖。線程進入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖。獲得內置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法。 java內置鎖是一個互斥鎖,這就是意味着最多只有一個線程能夠獲得該鎖,當線程A嘗試去獲得線程B持有的內置鎖時,線程A必須等待或者阻塞,知道線程B釋放這個鎖,如果B線程不釋放這個鎖,那么A線程將永遠等待下去。 java的對象鎖和類鎖:java的對象鎖和類鎖在鎖的概念上基本上和內置鎖是一致的,但是,兩個鎖實際是有很大的區別的,對象鎖是用於對象實例方法,或者一個對象實例上的,類鎖是用於類的靜態方法或者一個類的class對象上的。我們知道,類的對象實例可以有很多個,但是每個類只有一個class對象,所以不同對象實例的對象鎖是互不干擾的,但是每個類只有一個類鎖。但是有一點必須注意的是,其實類鎖只是一個概念上的東西,並不是真實存在的,它只是用來幫助我們理解鎖定實例方法和靜態方法的區別的 上面已經對鎖的一些概念有了一點了解,下面探討synchronized關鍵字的用法。 synchronized的用法:synchronized修飾方法和synchronized修飾代碼塊。 下面分別分析這兩種用法在對象鎖和類鎖上的效果。 對象鎖的synchronized修飾方法和代碼塊:
public class TestSynchronized { public void test1() { synchronized(this) { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } } public synchronized void test2() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestSynchronized myt2 = new TestSynchronized(); Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" ); Thread test2 = new Thread( new Runnable() { public void run() { myt2.test2(); } }, "test2" ); test1.start();; test2.start(); // TestRunnable tr=new TestRunnable(); // Thread test3=new Thread(tr); // test3.start();
} } test2 : 4 test2 : 3 test2 : 2 test2 : 1 test2 : 0 test1 : 4 test1 : 3 test1 : 2 test1 : 1 test1 : 0
上述的代碼,第一個方法時用了同步代碼塊的方式進行同步,傳入的對象實例是this,表明是當前對象,當然,如果需要同步其他對象實例,也不可傳入其他對象的實例;第二個方法是修飾方法的方式進行同步。因為第一個同步代碼塊傳入的this,所以兩個同步代碼所需要獲得的對象鎖都是同一個對象鎖,下面main方法時分別開啟兩個線程,分別調用test1和test2方法,那么兩個線程都需要獲得該對象鎖,另一個線程必須等待。上面也給出了運行的結果可以看到:直到test2線程執行完畢,釋放掉鎖,test1線程才開始執行。(可能這個結果有人會有疑問,代碼里面明明是先開啟test1線程,為什么先執行的是test2呢?這是因為java編譯器在編譯成字節碼的時候,會對代碼進行一個重排序,也就是說,編譯器會根據實際情況對代碼進行一個合理的排序,編譯前代碼寫在前面,在編譯后的字節碼不一定排在前面,所以這種運行結果是正常的, 這里是題外話,最主要是檢驗synchronized的用法的正確性) 如果我們把test2方法的synchronized關鍵字去掉,執行結果會如何呢? test1 : 4 test2 : 4 test2 : 3 test1 : 3 test1 : 2 test2 : 2 test2 : 1 test1 : 1 test2 : 0 test1 : 0 上面是執行結果,我們可以看到,結果輸出是交替着進行輸出的,這是因為,某個線程得到了對象鎖,但是另一個線程還是可以訪問沒有進行同步的方法或者代碼。進行了同步的方法(加鎖方法)和沒有進行同步的方法(普通方法)是互不影響的,一個線程進入了同步方法,得到了對象鎖,其他線程還是可以訪問那些沒有同步的方法(普通方法)。這里涉及到內置鎖的一個概念(此概念出自java並發編程實戰第二章):對象的內置鎖和對象的狀態之間是沒有內在的關聯的,雖然大多數類都將內置鎖用做一種有效的加鎖機制,但對象的域並不一定通過內置鎖來保護。當獲取到與對象關聯的內置鎖時,並不能阻止其他線程訪問該對象,當某個線程獲得對象的鎖之后,只能阻止其他線程獲得同一個鎖。之所以每個對象都有一個內置鎖,是為了免去顯式地創建鎖對象。 所以synchronized只是一個內置鎖的加鎖機制,當某個方法加上synchronized關鍵字后,就表明要獲得該內置鎖才能執行,並不能阻止其他線程訪問不需要獲得該內置鎖的方法。 類鎖的修飾(靜態)方法和代碼塊:
public class TestSynchronized { public void test1() { synchronized(TestSynchronized.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 test2() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestSynchronized myt2 = new TestSynchronized(); Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" ); Thread test2 = new Thread( new Runnable() { public void run() { TestSynchronized.test2(); } }, "test2" ); test1.start(); test2.start(); // TestRunnable tr=new TestRunnable(); // Thread test3=new Thread(tr); // test3.start();
} } test1 : 4 test1 : 3 test1 : 2 test1 : 1 test1 : 0 test2 : 4 test2 : 3 test2 : 2 test2 : 1 test2 : 0
其實,類鎖修飾方法和代碼塊的效果和對象鎖是一樣的,因為類鎖只是一個抽象出來的概念,只是為了區別靜態方法的特點,因為靜態方法是所有對象實例共用的,所以對應着synchronized修飾的靜態方法的鎖也是唯一的,所以抽象出來個類鎖。其實這里的重點在下面這塊代碼,synchronized同時修飾靜態和非靜態方法
public class TestSynchronized { public synchronized void test1() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static synchronized void test2() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestSynchronized myt2 = new TestSynchronized(); Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" ); Thread test2 = new Thread( new Runnable() { public void run() { TestSynchronized.test2(); } }, "test2" ); test1.start(); test2.start(); // TestRunnable tr=new TestRunnable(); // Thread test3=new Thread(tr); // test3.start();
} } test1 : 4 test2 : 4 test1 : 3 test2 : 3 test2 : 2 test1 : 2 test2 : 1 test1 : 1 test1 : 0 test2 : 0