進行多線程編程的時候,須要考慮的是線程間的同步問題。對於共享的資源,須要進行互斥的訪問。在Java中可使用一些手段來達到線程同步的目的:編程
1. synchronized 緩存
2. ThreadLocal,線程本地變量多線程
3. Java.util.concurrent.Lock併發
Java中,線程會共享堆上的實例變量以及方法區的類變量,而棧上的數據是私有的,沒必要進行保護。synchronized方法或synchronized塊將標記一塊監視區域,線程在進入該區域時,須要得到對象鎖或類鎖,JVM將自動上鎖。synchronized提供了兩種主要特性:ide
1. 互斥。互斥是指一次只容許一個線程持有某個特定的鎖,所以可以使用該特性實現對共享數據的併發訪問,保證一次只有一個線程可以使用該共享數據。函數
2.可見性。確保釋放鎖以前對共享數據作出的更改對隨後得到該鎖的另外一個線程是可見的。若是不能保證可見性,也就沒法保證數據正確性,這將引起嚴重問題。volitail關鍵字一樣保證了這種可見性。this
在這裏,咱們將探討synchronized使用時的三種狀況:spa
1. 在對象上使用synchronized線程
2. 在普通成員方法上使用synchronizedcode
3. 在靜態成員方法上使用synchronized
這三種線程同步的表現有何不一樣?
下面經過三段示例代碼來演示這三種狀況。這裏模擬線程報數的場景。
狀況一:在普通成員函數上使用synchronized
public class MyThread extends Thread { public static void main(String[] args) throws Exception { for (int i = 1; i < 100; i++) { MyThread t = new MyThread(); t.setName("Thread="+i); t.start(); Thread.sleep(100); } } @Override public synchronized void run() { for (int i = 1; i < 10000; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }
對一個成員函數使用synchronized進行加鎖,所獲取的鎖,是方法所在對象自己的對象鎖。在這裏,每一個線程都以自身的對象做爲對象鎖,要對線程進行同步,要求鎖對象必須惟一,因此這裏多個線程間同步失敗。
狀況二:在對象上使用synchronized
這裏在類中增長一個成員變量lock,在該變量上使用synchronized:
public class MyThread1 extends Thread { private String lock; public MyThread1(String lock) { this.lock = lock; } public static void main(String[] args) throws Exception { String lock = new String("lock"); for (int i = 1; i < 100; i++) { Thread t = new MyThread1(lock); t.setName("Thread=" + i); t.start(); Thread.sleep(100); } } @Override public void run() { synchronized (lock) { for (int i = 1; i < 10000; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } }
100個線程在建立的時候,都傳遞了同一個lock對象(在main中建立的)去初始化線程類成員lock,所以,這100個線程都在同一個lock對象上進行synchronized同步。所以線程同步成功。
狀況三:在靜態成員函數上使用synchronized
public class MyThread2 extends Thread { public static void main(String[] args) throws Exception { for (int i = 1; i < 10; i++) { Thread t = new MyThread2(); t.setName("Thread=" + i); t.start(); Thread.sleep(10); } } public static synchronized void func() { for (int i = 1; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } @Override public void run() { func(); } }
這種狀況下,線程得到的鎖是對象鎖,而對象鎖是惟一的,所以多個進程間也能同步成功。
補充:
1. 慎用字符串常量作同步對象,由於JVM內部會把常量字符串轉換成同一個對象,同理的,基本數據除了Float和Double外,也有緩存對象[-128,127].
2. synchronized方法繼承問題:1. 子類會繼承父類的synchronized方法。2. 若是子類重寫了父類的synchronized方法,必須也加上synchronized關鍵字,不然子類中的方法將變成非同步的。 3. 同一個子類對象中,子類的synchronized方法父類的synchronized方法使用的是同一個臨界區。
(完)