共用資料的並發處理
多線程同時並發訪問的資源叫做臨界資源。
多個線程同時訪問對象並要求操作相同資源時分割了原子操作就會出現問題。(原子操作,不可再分的操作)會出現資料的不一致或資料不完整,為避免這種現象採用對訪問的線程做限制的方法。
互斥鎖機制,利用每個對象都有一個monitor(鎖標記),當線程擁有這個鎖標記時才能訪問這個資源,沒有鎖標記便進入鎖池。任何一個對象系統都會為其建立一個互斥鎖,這個瑣是為了分配給線程的,防止打斷原子操作。每個對象的鎖只能分配給一個線程。
1.Synchronized修飾代碼塊(同步代碼塊),
public void push(char c){
synchronized(this){//只有持有當前對象的鎖標記才能訪問這個代碼塊
...
}
}
對括弧內的對象加鎖,只有拿到鎖標記的對象才能執行該代碼塊
2.Synchronized修飾方法
public synchronized void push(char c) {
...
}
對當前對象的加鎖,只有拿到鎖標記的對象才能執行該方法。
注意:構造方法不能Synchronized修飾,靜態方法可以用Synchronized修飾(是對類對象加鎖),抽象方法不能用Synchronized修飾,不影響子類覆蓋,子類在覆蓋這個方法是可以加Synchronized,也可以不加Synchronized,所以根據Java不允許寫廢代碼的特點是不能寫在一起。
注意:對當前對象加鎖,一個代碼塊或者方法是同步的(Synchronized),當前對象的鎖標記沒有分配出去時,有一個線程來訪問這個代碼塊時,就會的到這個對象的鎖標記,直到這個線程結束才會釋放著個鎖標記,其他想訪問這個代碼塊或者是方法線程就會進入這個對象鎖池,如果沒有得到當前對象的鎖標記,就不能訪問這個代碼塊或者是方法。當一個線程想要獲得某個對象鎖標記而進入鎖池,這個線程又持有其他對象的鎖標記,那麼這個線程也不會釋放持有的鎖標記。
註:方法的Synchronized特性本身不會被繼承,只能覆蓋。
線程因為未拿到鎖標記而發生阻塞進入鎖池(lock pool)。每個對象都有自己的一個鎖池的空間,用於放置等待啟動並執行線程。由系統決定哪個線程拿到鎖標記並運行。
使用互斥鎖的注意事項
舉例:男孩和女孩例子,每個女孩是一個對象,每個男孩是個線程。每個女孩都有自己的鎖池。每個男孩可能在鎖池裡等待。
class Gril {
public void hand() {
}
public synchronized void kiss() {
}
}
class Boy extends Thread {
public void run() {
}
}
注意:唯讀不用加同步,唯寫也不用加同步,只有讀寫操作兼而有之時才加同步。
注意:在java.io包中Vector 和 HashTable 之所以是安全執行緒的,是因為每個方法都有synchronized修飾,Static 方法可以加 synchronized , 鎖的是類對象。但是Vector 是 jdk 1.0 的 ArrayList 是 jdk1.2 所以實際應用還是使用ArrayList。
注意:內同步,外同步。內同步,即,類內的方法加同步(synchronized)修飾,外同步即,在需要控制只能由一個線程進行訪問時,把需要控制的方法寫在同步代碼塊裡。
死結問題
多線程不釋放自己擁有的鎖標記,而想申請其他線程擁有的鎖標記,就會造成死結。
沒有獲得加鎖對象的鎖標記的線程,不能訪問只有獲得該對象所標記才能訪問的同步方法,但可以訪問這個對象的非同步的方法。
死結的兩種處理方法
統一排列鎖順序(解決不同方法中對多個共用資源的訪問)
對象1的方法
synchronized(a)
synchronized(b)
對象2的方法
synchronized(a)
synchronized(b)
java線程間通訊(也就是線程間的相互協調)
等待通知機制
線程間通訊使用的空間稱之為對象的等待對列(wait pool),該隊列也是屬於對象的空間的。
進入等待池
使用Object類中wait()的方法,在運行狀態中,線程調用wait(),此時表示線程將釋放自己所有的鎖標記和CPU的佔用,同時進入這個對象的等待池。等待池的狀態也是阻塞狀態,只不過線程釋放自己的鎖標記。只有在對該對象加鎖的同步代碼塊裡,才能掉用該對象的wait(),表示線程將會釋放所有鎖標記,進入等待隊列,線程將進入等待隊列狀態。
一個線程進入了對一個對象加鎖的同步代碼塊,並對該對象調用了wait()方法,釋放自己擁有的所有鎖標記,進入該對象等待隊列,另一個線程獲得了該對象的鎖標記,進入代碼塊對該對象調用了notify()方法(如果對該對象調用了notifyAll()方法,就會使放等待隊列裡所有的線程),對該對象調用方法的線程也不會釋放所擁有的鎖標記(對自身沒有影響),也就是從等待隊列裡釋放出一線程,釋放出的這個線程要繼續運行也就還要進入那個同步代碼塊,因為得不到要存取碼塊對象的鎖標記,而進入該對象的鎖池,等待所標記釋放。
注意:用notifyAll()取代notify(),因為在調用notify()方法時,是由系統決定釋放出哪個線程。
退出等待池進入鎖池
notify():將從對象的等待池中移走一個任意的線程,並放到鎖池中,那裡的對象一直在等待,直到可以獲得對象的鎖標記。
notifyAll(): 將從等待池中移走所有等待那個對象的線程並放到鎖池中,只有鎖池中的線程能擷取對象的鎖標記,鎖標記允許線程從上次因調用wait()而中斷的地方開始繼續運行
注意:只能對加鎖的資源進行wait()和notify()。
1) wait():交出鎖和CPU的佔用;
2) notify():將從對象的等待池中移走一個任意的線程,並放到鎖池中,那裡的對象一直在等待,直到可以獲得對象的鎖標記。
3) notifyAll(): 將從等待池中移走所有等待那個對象的線程並放到鎖池中,只有鎖池中的線程能擷取對象的鎖標記,鎖標記允許線程從上次因調用wait()而中斷的地方開始繼續運行