在java編程中,經常需要用到同步,而用得最多的也許是synchronized關鍵字了,下面看看這個關鍵字的用法。
因為synchronized關鍵字涉及到鎖的概念,所以先來了解一些相關的鎖知識。
java的內置鎖:每個java對象都可以用做一個實現同步的鎖,這些鎖成為內置鎖。線程進入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖。獲得內置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法。
java內置鎖是一個互斥鎖,這就是意味着最多只有一個線程能夠獲得該鎖,當線程A嘗試去獲得線程B持有的內置鎖時,線程A必須等待或者阻塞,知道線程B釋放這個鎖,如果B線程不釋放這個鎖,那么A線程將永遠等待下去。
java的對象鎖和類鎖:java的對象鎖和類鎖在鎖的概念上基本上和內置鎖是一致的,但是,兩個鎖實際是有很大的區別的,對象鎖是用於對象實例方法,或者一個對象實例上的,類鎖是用於類的靜態方法或者一個類的class對象上的。我們知道,類的對象實例可以有很多個,但是每個類只有一個class對象,所以不同對象實例的對象鎖是互不干擾的,但是每個類只有一個類鎖。但是有一點必須注意的是,其實類鎖只是一個概念上的東西,並不是真實存在的,它只是用來幫助我們理解鎖定實例方法和靜態方法的區別的。
1、對象鎖的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();
- }
- }
上述的代碼,第一個方法時用了同步代碼塊的方式進行同步,傳入的對象實例是this,表明是當前對象,當然,如果需要同步其他對象實例,也不可傳入其他對象的實例;第二個方法是修飾方法的方式進行同步。因為第一個同步代碼塊傳入的this,所以兩個同步代碼所需要獲得的對象鎖都是同一個對象鎖,下面main方法時分別開啟兩個線程,分別調用test1和test2方法,那么兩個線程都需要獲得該對象鎖,另一個線程必須等待。
2、類鎖的修飾(靜態)方法和代碼塊:
- 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();
- }
- }
類鎖修飾方法和代碼塊的效果和對象鎖是一樣的,因為類鎖只是一個抽象出來的概念,只是為了區別靜態方法的特點,因為靜態方法是所有對象實例共用的,所以對應着synchronized修飾的靜態方法的鎖也是唯一的,所以抽象出來個類鎖。那么兩個線程都需要獲得該對象鎖,另一個線程必須等待。
3、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();
- }
- }
上面代碼synchronized同時修飾靜態方法和實例方法,但是運行結果是交替進行的,這證明了類鎖和對象鎖是兩個不一樣的鎖,控制着不同的區域,它們是互不干擾的。同樣,線程獲得對象鎖的同時,也可以獲得該類鎖,即同時獲得兩個鎖,這是允許的。
到這里,對synchronized的用法已經有了一定的了解。這時有一個疑問,既然有了synchronized修飾方法的同步方式,為什么還需要synchronized修飾同步代碼塊的方式呢?而這個問題也是synchronized的缺陷所在
synchronized的缺陷:當某個線程進入同步方法獲得對象鎖,那么其他線程訪問這里對象的同步方法時,必須等待或者阻塞,這對高並發的系統是致命的,這很容易導致系統的崩潰。如果某個線程在同步方法里面發生了死循環,那么它就永遠不會釋放這個對象鎖,那么其他線程就要永遠的等待。這是一個致命的問題。
一個類的對象鎖和另一個類的對象鎖是沒有關聯的,當一個線程獲得A類的對象鎖時,它同時也可以獲得B類的對象鎖。