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()。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章