Java併發編程之飢渴與公平
如果一個線程沒有被授予CPU時間,因爲其他線程攫取了它的全部CPU時間,這被稱爲“飢餓”。這個線程“餓死了”,因爲其他線程被允許佔用CPU時間。解決飢餓問題的方法稱爲“公平”——即所有線程都被公平地給予執行機會。
產生飢渴的原因
高優先級的線程從低優先級的線程中吞噬所有CPU時間
您可以分別設置每個線程的線程優先級。優先級越高,線程被授予的CPU時間就越多。可以將線程的優先級設置爲1到10。具體如何解釋取決於應用程序所運行的操作系統。對於大多數應用程序,最好保持優先級不變。
線程被不確定地阻塞以等待進入一個同步塊,因爲其他線程總是被允許在它之前訪問它。
Java的同步代碼塊可能是導致資源短缺的另一個原因。Java的同步代碼塊不能保證等待進入同步塊的線程被允許進入的順序。這意味着理論上存在這樣的風險,即一個線程在嘗試進入塊時永遠處於阻塞狀態,因爲其他線程總是在它之前被授予訪問權。這個問題被稱爲“飢餓”,即一個線程“餓死”,因爲其他線程被允許佔用CPU時間
等待對象的線程(調用該對象上的wait())會無限期地等待,因爲其他線程會不斷地被喚醒
如果調用notify()對象的多個線程調用了wait(),則notify()方法不能保證會喚醒哪個線程。它可以是任何等待的線程。因此,存在等待某個對象的線程永遠不會被喚醒的風險,因爲總是有其他等待的線程被喚醒。
使用同步塊實現公平競爭
public class MyClass{
//聲明同步代碼塊
public synchronized void doSynchronized(){
//dosomething
}
}
如果有多個線程調用doSynchronized()方法,其中一些線程將被阻塞,直到第一個被授予訪問權限的線程離開該方法。如果阻塞了多個線程等待訪問,則無法保證下一個線程被授予訪問權限。
使用Lock鎖實現公平競爭
public class MyClass{
Lock lock = new Lock();
public void doSynchronized() throws InterruptedException{
this.lock.lock();
//需要鎖的部分
this.lock.unlock();
}
}
public class Lock{
private boolean isLock = false;
private Thread lockingThread = null;
public synchronized void lock() throws InterruptedException{
while(isLock){
wait();
}
this.lock = true;
lockingThread = Thread.currentThread();
}
public synchronized void unlock() throws InterruptedException{
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
this.isLock = false;
this.lockingThread = null;
notify();
}
}
如果多個線程同時調用Lock(),那麼現在嘗試訪問Lock()方法的線程會被阻塞。其次,如果鎖被鎖定,那麼線程將在lock()方法的while(isLocked)循環中的wait()調用中被阻塞。記住,調用wait()的線程釋放鎖實例上的同步鎖,因此等待進入lock()的線程現在可以這樣做。結果是,多個線程可能最終在lock()中調用了wait().
使用隊列達到公平
public class MyQueueObject{
private boolean isNotified = false;
public synchronized void doWait() throws InterruptedException{
while(isNotified){
this.wait();
}
this.isNotified = false;
}
public synchronized void doNotify(){
this.isNotified = true;
this.notify();
}
public boolean equals(Object o){
return this == o;
}
}
public class MyLock{
private boolean isLock = false;
private Thread lockingThread = null;
private List<MyQueueObject> waitingThreads = new ArrayList<>();
public void lock() throws InterruptedException{
MyQueueObject myQueueObject = new MyQueueObject ();
boolean isLockedForThread = true;
synchronized(this){
waitingThreads .add(myQueueObject);
}
while(isLockedForThread ){
synchronized(this){
isLockedForThread = isLock || waitingThreads.get(0) != myQueueObject;
if(!isLockedForThread ){
isLock = true;
waitingThreads.remove(myQueueObject);
lockingThread = Thread.currentThread();
return ;
}
}
}
try{
queueObject.doWait();
}catch(InterruptedException e){
synchronized(this) { waitingThreads.remove(myQueueObject); }
throw e;
}
}
}
public synchronized void unlock(){
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
isLock = false;
lockingThread = null;
if(waitingThreads.size() > 0){
waitingThreads.get(0).doNotify();
}
}
}
MyLock創建一個MyQueueObject的新實例,併爲每個調用lock()的線程排隊。調用unlock()的線程將獲取隊列中最頂端的MyQueueObject並對其調用doNotify(),以喚醒等待該對象的線程。這樣,一次只喚醒一個等待的線程,而不是所有等待的線程。這部分決定了MyLock的公平性。
還要注意,MyQueueObject實際上是一個信號量。doWait()和doNotify()方法將信號內部存儲在MyQueueObject中。這樣做是爲了避免一個線程在調用myQueueObject.doWait()之前被另一個調用unlock()從而調用myQueueObject.doNotify()的線程搶佔而導致的信號丟失。myQueueObject.doWait()調用被放在synchronized(this)塊之外,以避免嵌套的監視器鎖定,因此,當lock()方法中的synchronized(this)塊中沒有線程執行時,另一個線程實際上可以調用unlock()。