對代碼進行同步控制我們可以選擇同步方法,也可以選擇同步塊,這兩種方式各有優缺點,同步塊不僅可以更加精確的控制物件鎖,還可以控制鎖的作用域,何謂鎖的作用域?鎖的作用域就是從鎖被獲取到其被釋放的時間,而且可以選擇要獲取哪個物件的物件鎖,但是如果在使用同步塊機制時,如果使用過多的鎖也會容易引起死鎖問題,同時獲取和釋放所也有代價,而同步方法,它們所擁有的鎖就是該方法所屬的類的物件鎖,換句話說,也就是this物件,而且鎖的作用域也是整個方法,這可能導致其鎖的作用域可能太大,也有可能引起死鎖,同時因為可能包含了不需要進行同步的代碼塊在內,也會降低程式的運行效率,而不管是同步方法還是同步塊,我們都不應該在他們的代碼塊內包含無限回圈,如果代碼內部要是有了無限回圈,那么這個同步方法或者同步塊在獲取鎖以后因為代碼會一直不停的回圈著運行下去,也就沒有機會釋放它所獲取的鎖,而其它等待這把鎖的執行緒就永遠無法獲取這把鎖,這就造成了一種死鎖現象,
詳細解說一下同步方法的鎖,同步方法分為靜態同步方法與非靜態同步方法,先看代碼再說理論,以下的案例,如果show方法不是靜態的即使被synchronized修飾也無法解決執行緒問題,大家可以先試一下
class Windows implements Runnable {
private static int ticketNum = 10;
public static synchronized void show(){
String name = Thread.currentThread().getName();
if(ticketNum > 0) {
try {
//這一步是為了演示錯票,原理是當前執行緒進入了if陳述句陷入沉睡的時候票被賣光,
//然后當該執行緒蘇醒時再來一次ticketNum--產生0號這個非法票
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name +"賣出第" + ticketNum + "張票");
ticketNum--;
}
}
@Override
public void run() {
while (true){
show();
}
}
}
public class ThreadTest{
public static void main(String[] args) {
Windows windows = new Windows();
Windows windows1 = new Windows();
Thread thread1 = new Thread(windows);
thread1.setName("視窗1");
Thread thread2 = new Thread(windows);;
thread2.setName("視窗2");
Thread thread3 = new Thread(windows1);
thread3.setName("視窗3");
thread1.start();
thread2.start();
thread3.start();
}
}
運行結果,沒有執行緒安全問題

為何show必須是靜態同步才能解決執行緒問題?
1 所有的非靜態同步方法用的都是同一把鎖——實體物件本身(this,本例中有windows和windows1)也就是說如果一個實體物件的非靜態同步方法獲取鎖后,該實體物件的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖后才能獲取鎖(本例中沒有體現,大家可以參考我的博客:生產者消費者案例),可是別的實體物件(windows1)的非靜態同步方法因為跟該實體物件(windows)的非靜態同步方法用的是不同的鎖,所以毋須等待該實體物件已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖,自然無法保證執行緒安全,
2 而所有的靜態同步方法用的也是同一把鎖——類物件本身(Windwos.class),這兩把鎖是兩個不同的物件,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的,但是一旦一個靜態同步方法獲取鎖后,其他的靜態同步方法都必須等待該方法釋放鎖后才能獲取鎖,而不管是同一個實體物件的靜態同步方法之間,還是不同的實體物件的靜態同步方法之間,只要它們同一個類的實體物件!
同理,看以下代碼,我們選擇使用Windows.class作為同步鎖,可以保證執行緒安全,但是如果我們還是選用this做同步鎖依然無法保證執行緒安全
class Windows implements Runnable {
private static int ticketNum = 10;
@Override
public void run() {
String name = Thread.currentThread().getName();
while (true){
//這里也推薦用this(this代表的是main中的windows物件)或Windows.class
synchronized (Windows.class){
if(ticketNum > 0) {
try {
//這一步是為了演示錯票,原理是當前執行緒進入了if陳述句陷入沉睡的時候票被賣光,
//然后當該執行緒蘇醒時再來一次ticketNum--產生0號這個非法票
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "賣出第" + ticketNum + "張票");
ticketNum--;
}
}
}
}
}
public class ThreadTest{
public static void main(String[] args) {
Windows windows = new Windows();
Windows windows1 = new Windows();
Thread thread1 = new Thread(windows);
thread1.setName("視窗1");
Thread thread2 = new Thread(windows);;
thread2.setName("視窗2");
Thread thread3 = new Thread(windows1);
thread3.setName("視窗3");
thread1.start();
thread2.start();
thread3.start();
}
}
對于同步塊,由于其鎖是可以選擇的,所以只有使用同一把鎖的同步塊之間才有著競態條件,這就得具體情況具體分析了,但這里有個需要注意的地方,同步塊的鎖是可以選擇的,但是不是可以任意選擇的!!!!這里必須要注意一個物理物件和一個參考物件的實體變數之間的區別!使用一個參考物件的實體變數作為鎖并不是一個好的選擇,因為同步塊在執行程序中可能會改變它的值,其中就包括將其設定為null,而對一個null物件加鎖會產生例外,并且對不同的物件加鎖也違背了同步的初衷!這看起來是很清楚的,但是一個經常發生的錯誤就是選用了錯誤的鎖物件,因此必須注意:同步是基于實際物件而不是物件參考的!多個變數可以參考同一個物件,變數也可以改變其值從而指向其他的物件,因此,當選擇一個物件鎖時,我們要根據實際物件而不是其參考來考慮!作為一個原則,不要選擇一個可能會在鎖的作用域中改變值的實體變數作為鎖物件!!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/255148.html
標籤:其他
下一篇:Java學習之旅
