一、什麼時候會出現線程安全問題?
當多個線程同時訪問一個資源(共享資源)時會出現線程安全。資源可以是一個變量、一個對象、一個文件、一個數據庫表等。需要注意的是如果多個程序同時訪問一個方法,定義在方法內部的局部變量並不是臨界資源(共享資源),因爲方法是在棧中執行的,而棧是線程私有的,因此不會出現線程安全問題。
二、如何解決線程安全問題?
通常來說在通過對訪問共享資源代碼加鎖,當一個線程來訪問時加鎖,其他線程等待這個線程釋放鎖,當前線程執行完後加鎖代碼後釋放鎖資源。其他線程就可以訪問共享資源了。在Java中,每一個對象都擁有一個鎖標記(monitor),也稱爲監視器,多線程同時訪問某個對象時,線程只有獲取了該對象的鎖才能訪問。
Java提供了兩種方式來實現同步互斥訪問:synchronized和Lock。
三、synchronized同步代碼塊和同步方法代碼示例
package sync;
public class TestSynchronized {
static TestSynchronized ts = new TestSynchronized();//所有線程共用這一個鎖
public static void main(String[] args) {
new TestSynchronized().new Thread1().start();
new TestSynchronized().new Thread2().start();
}
private void invoke() {
try {
synchronized (ts) {//同步代碼塊
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + "寫數據庫" + i);
Thread.sleep(100);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class Thread1 extends Thread{
public void run(){
ts.invoke();
}
}
class Thread2 extends Thread{
public void run(){
ts.invoke();
}
}
}
運行結果:Thread-0和Thread-1依次執行,沒有出現相互競爭執行。
Thread-0:寫數據庫0
Thread-0:寫數據庫1
Thread-0:寫數據庫2
Thread-0:寫數據庫3
Thread-0:寫數據庫4
Thread-1:寫數據庫0
Thread-1:寫數據庫1
Thread-1:寫數據庫2
Thread-1:寫數據庫3
Thread-1:寫數據庫4
如果將鎖加在方法上執行,結果依然一樣:
private synchronized void invoke() {
try {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + "寫數據庫" + i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
運行結果:Thread-0:寫數據庫0
Thread-0:寫數據庫1
Thread-0:寫數據庫2
Thread-0:寫數據庫3
Thread-0:寫數據庫4
Thread-1:寫數據庫0
Thread-1:寫數據庫1
Thread-1:寫數據庫2
Thread-1:寫數據庫3
Thread-1:寫數據庫4
四、同步方法和同步代碼塊的區別。
synchronized代碼塊使用起來比synchronized方法要靈活得多。因爲也許一個方法中只有一部分代碼只需要同步,如果此時對整個方法用synchronized進行同步,會影響程序執行效率。而使用synchronized代碼塊就可以避免這個問題,synchronized代碼塊可以實現只對需要同步的地方進行同步。
五、synchronized需要注意的幾點。
1)當一個線程訪問一個對象的synchronized方法func1其他線程不能訪問這個對象方法func1,因爲一個對象只有一個鎖。只有釋放了鎖才能訪問func1。
2)當一個線程訪問一個對象的synchronized方法func1其他線程可以訪問這個對象的非synchronized方法,因爲其他方法不需要該對象的鎖。
3)如果一個類有static synchronized方法f1和synchronized方法f2,當兩個線程分別訪問f1和f2時是不會發生互斥的問題,因爲一個是對象鎖一個是類鎖,不是同一個鎖。
4)如果synchronized方法或者synchronized代碼塊發生異常,是不會出現由於異常導致死鎖,因爲發生異常後JVM會釋放鎖,這個Lock不一樣,lock需要手動釋放鎖。