欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java多線程饑餓與公平介紹及代碼示例

 更新時間:2017年11月03日 09:09:30   投稿:mengwei  
這篇文章主要介紹了Java多線程饑餓與公平介紹及代碼示例,分析饑餓產(chǎn)生的原因以及相關(guān)實(shí)例,然后就在java中實(shí)現(xiàn)公平性問題做了詳細(xì)解析,具有一定參考價值,需要的朋友可以了解下。

如果一個線程因?yàn)镃PU時間全部被其他線程搶走而得不到CPU運(yùn)行時間,這種狀態(tài)被稱之為“饑餓”。而該線程被“饑餓致死”正是因?yàn)樗貌坏紺PU運(yùn)行時間的機(jī)會。解決饑餓的方案被稱之為“公平性” – 即所有線程均能公平地獲得運(yùn)行機(jī)會。

 下面是本文討論的主題:

Java中導(dǎo)致饑餓的原因

在Java中,下面三個常見的原因會導(dǎo)致線程饑餓:

高優(yōu)先級線程吞噬所有的低優(yōu)先級線程的CPU時間。
線程被永久堵塞在一個等待進(jìn)入同步塊的狀態(tài),因?yàn)槠渌€程總是能在它之前持續(xù)地對該同步塊進(jìn)行訪問。
線程在等待一個本身(在其上調(diào)用wait())也處于永久等待完成的對象,因?yàn)槠渌€程總是被持續(xù)地獲得喚醒。

高優(yōu)先級線程吞噬所有的低優(yōu)先級線程的CPU時間

你能為每個線程設(shè)置獨(dú)自的線程優(yōu)先級,優(yōu)先級越高的線程獲得的CPU時間越多,線程優(yōu)先級值設(shè)置在1到10之間,而這些優(yōu)先級值所表示行為的準(zhǔn)確解釋則依賴于你的應(yīng)用運(yùn)行平臺。對大多數(shù)應(yīng)用來說,你最好是不要改變其優(yōu)先級值。

線程被永久堵塞在一個等待進(jìn)入同步塊的狀態(tài)

Java的同步代碼區(qū)也是一個導(dǎo)致饑餓的因素。Java的同步代碼區(qū)對哪個線程允許進(jìn)入的次序沒有任何保障。這就意味著理論上存在一個試圖進(jìn)入該同步區(qū)的線程處于被永久堵塞的風(fēng)險,因?yàn)槠渌€程總是能持續(xù)地先于它獲得訪問,這即是“饑餓”問題,而一個線程被“饑餓致死”正是因?yàn)樗貌坏紺PU運(yùn)行時間的機(jī)會。

線程在等待一個本身(在其上調(diào)用wait())也處于永久等待完成的對象

如果多個線程處在wait()方法執(zhí)行上,而對其調(diào)用notify()不會保證哪一個線程會獲得喚醒,任何線程都有可能處于繼續(xù)等待的狀態(tài)。因此存在這樣一個風(fēng)險:一個等待線程從來得不到喚醒,因?yàn)槠渌却€程總是能被獲得喚醒。

下面分享一個線程饑餓死鎖的例子,代碼:

public class ThreadDeadlock {
	ExecutorService exec = Executors.newSingleThreadScheduledExecutor();
	//  ExecutorService exec = Executors.newCachedThreadPool();  //如果添加給線程池中添加足夠多的線程,就可以讓所有任務(wù)都執(zhí)行,避免饑餓死鎖。 
	/** 
   * 模擬頁面加載的例子 
   * 
   * 產(chǎn)生死鎖分析: 
   * RenderPageTask任務(wù)中有2個子任務(wù)分別是“加載頁眉”和“加載頁腳”。當(dāng)提交RenderPageTask任務(wù)時,實(shí)際上是向線程池中添加了3個任務(wù), 
   * 但是由于線程池是單一線程池,同時只會執(zhí)行一個任務(wù),2個子任務(wù)就會在阻塞在線程池中。而RenderPageTask任務(wù)由于得不到返回,也會 
   * 一直堵塞,不會釋放線程資源讓子線程執(zhí)行。這樣就導(dǎo)致了線程饑餓死鎖。 
   * 
   * 在一個Callable任務(wù)中,要返回2個子任務(wù) 
   * @author hadoop 
   * 
   */
	class RenderPageTask implements Callable<String>{
		@Override 
		    public String call() throws Exception {
			Future<String> header,footer;
			header = exec.submit(new Callable<String>(){
				@Override 
				        public String call() throws Exception {
					System.out.println("加載頁眉");
					Thread.sleep(2*1000);
					return "頁眉";
				}
			}
			);
			footer = exec.submit(new Callable<String>(){
				@Override 
				        public String call() throws Exception {
					System.out.println("加載頁腳");
					Thread.sleep(3*1000);
					return "頁腳";
				}
			}
			);
			System.out.println("渲染頁面主體");
			return header.get() + footer.get();
		}
	}
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ThreadDeadlock td = new ThreadDeadlock();
		Future<String> futre = td.exec.submit(td.new RenderPageTask());
		String result = futre.get();
		System.out.println("執(zhí)行結(jié)果為:" + result);
	}
}

在Java中實(shí)現(xiàn)公平性

雖Java不可能實(shí)現(xiàn)100%的公平性,我們依然可以通過同步結(jié)構(gòu)在線程間實(shí)現(xiàn)公平性的提高。

首先來學(xué)習(xí)一段簡單的同步態(tài)代碼:

public class Synchronizer{
  public synchronized void doSynchronized(){
  //do a lot of work which takes a long time
  }
}

如果有一個以上的線程調(diào)用doSynchronized()方法,在第一個獲得訪問的線程未完成前,其他線程將一直處于阻塞狀態(tài),而且在這種多線程被阻塞的場景下,接下來將是哪個線程獲得訪問是沒有保障的。

使用鎖方式替代同步塊

為了提高等待線程的公平性,我們使用鎖方式來替代同步塊。

public class Synchronizer{
  Lock lock = new Lock();
  public void doSynchronized() throws InterruptedException{
    this.lock.lock();
    //critical section, do a lot of work which takes a long time
    this.lock.unlock();
  }
}

注意到doSynchronized()不再聲明為synchronized,而是用lock.lock()和lock.unlock()來替代。

下面是用Lock類做的一個實(shí)現(xiàn):

public class Lock{
	private Boolean isLocked   = false;
	private Thread lockingThread = null;
	public synchronized void lock() throws InterruptedException{
		while(isLocked){
			wait();
		}
		isLocked = true;
		lockingThread = Thread.currentThread();
	}
	public synchronized void unlock(){
		if(this.lockingThread != Thread.currentThread()){
			throw new IllegalMonitorStateException(
			       "Calling thread has not locked this lock");
		}
		isLocked = false;
		lockingThread = null;
		notify();
	}
}

注意到上面對Lock的實(shí)現(xiàn),如果存在多線程并發(fā)訪問lock(),這些線程將阻塞在對lock()方法的訪問上。另外,如果鎖已經(jīng)鎖上(校對注:這里指的是isLocked等于true時),這些線程將阻塞在while(isLocked)循環(huán)的wait()調(diào)用里面。要記住的是,當(dāng)線程正在等待進(jìn)入lock() 時,可以調(diào)用wait()釋放其鎖實(shí)例對應(yīng)的同步鎖,使得其他多個線程可以進(jìn)入lock()方法,并調(diào)用wait()方法。

這回看下doSynchronized(),你會注意到在lock()和unlock()之間的注釋:在這兩個調(diào)用之間的代碼將運(yùn)行很長一段時間。進(jìn)一步設(shè)想,這段代碼將長時間運(yùn)行,和進(jìn)入lock()并調(diào)用wait()來比較的話。這意味著大部分時間用在等待進(jìn)入鎖和進(jìn)入臨界區(qū)的過程是用在wait()的等待中,而不是被阻塞在試圖進(jìn)入lock()方法中。

在早些時候提到過,同步塊不會對等待進(jìn)入的多個線程誰能獲得訪問做任何保障,同樣當(dāng)調(diào)用notify()時,wait()也不會做保障一定能喚醒線程(至于為什么,請看線程通信)。因此這個版本的Lock類和doSynchronized()那個版本就保障公平性而言,沒有任何區(qū)別。

但我們能改變這種情況。當(dāng)前的Lock類版本調(diào)用自己的wait()方法,如果每個線程在不同的對象上調(diào)用wait(),那么只有一個線程會在該對象上調(diào)用wait(),Lock類可以決定哪個對象能對其調(diào)用notify(),因此能做到有效的選擇喚醒哪個線程。

公平鎖

下面來講述將上面Lock類轉(zhuǎn)變?yōu)楣芥iFairLock。你會注意到新的實(shí)現(xiàn)和之前的Lock類中的同步和wait()/notify()稍有不同。
準(zhǔn)確地說如何從之前的Lock類做到公平鎖的設(shè)計(jì)是一個漸進(jìn)設(shè)計(jì)的過程,每一步都是在解決上一步的問題而前進(jìn)的:Nested Monitor Lockout, Slipped Conditions和Missed Signals。這些本身的討論雖已超出本文的范圍,但其中每一步的內(nèi)容都將會專題進(jìn)行討論。重要的是,每一個調(diào)用lock()的線程都會進(jìn)入一個隊(duì)列,當(dāng)解鎖后,只有隊(duì)列里的第一個線程被允許鎖住Farlock實(shí)例,所有其它的線程都將處于等待狀態(tài),直到他們處于隊(duì)列頭部。

public class FairLock {
	private Boolean      isLocked    = false;
	private Thread      lockingThread = null;
	private List<QueueObject> waitingThreads =
	      new ArrayList<QueueObject>();
	public void lock() throws InterruptedException{
		QueueObject queueObject      = new QueueObject();
		Boolean   isLockedForThisThread = true;
		synchronized(this){
			waitingThreads.add(queueObject);
		}
		while(isLockedForThisThread){
			synchronized(this){
				isLockedForThisThread =
				      isLocked || waitingThreads.get(0) != queueObject;
				if(!isLockedForThisThread){
					isLocked = true;
					waitingThreads.remove(queueObject);
					lockingThread = Thread.currentThread();
					return;
				}
			}
			try{
				queueObject.doWait();
			}
			catch(InterruptedException e){
				synchronized(this) {
					waitingThreads.remove(queueObject);
				}
				throw e;
			}
		}
	}
	public synchronized void unlock(){
		if(this.lockingThread != Thread.currentThread()){
			throw new IllegalMonitorStateException(
			    "Calling thread has not locked this lock");
		}
		isLocked   = false;
		lockingThread = null;
		if(waitingThreads.size() > 0){
			waitingThreads.get(0).doNotify();
		}
	}
}
public class QueueObject {
	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;
	}
}

首先注意到lock()方法不在聲明為synchronized,取而代之的是對必需同步的代碼,在synchronized中進(jìn)行嵌套。

FairLock新創(chuàng)建了一個QueueObject的實(shí)例,并對每個調(diào)用lock()的線程進(jìn)行入隊(duì)列。調(diào)用unlock()的線程將從隊(duì)列頭部獲取QueueObject,并對其調(diào)用doNotify(),以喚醒在該對象上等待的線程。通過這種方式,在同一時間僅有一個等待線程獲得喚醒,而不是所有的等待線程。這也是實(shí)現(xiàn)FairLock公平性的核心所在。

請注意,在同一個同步塊中,鎖狀態(tài)依然被檢查和設(shè)置,以避免出現(xiàn)滑漏條件。

還需注意到,QueueObject實(shí)際是一個semaphore。doWait()和doNotify()方法在QueueObject中保存著信號。這樣做以避免一個線程在調(diào)用queueObject.doWait()之前被另一個調(diào)用unlock()并隨之調(diào)用queueObject.doNotify()的線程重入,從而導(dǎo)致信號丟失。queueObject.doWait()調(diào)用放置在synchronized(this)塊之外,以避免被monitor嵌套鎖死,所以另外的線程可以解鎖,只要當(dāng)沒有線程在lock方法的synchronized(this)塊中執(zhí)行即可。

最后,注意到queueObject.doWait()在try – catch塊中是怎樣調(diào)用的。在InterruptedException拋出的情況下,線程得以離開lock(),并需讓它從隊(duì)列中移除。

性能考慮

如果比較Lock和FairLock類,你會注意到在FairLock類中l(wèi)ock()和unlock()還有更多需要深入的地方。這些額外的代碼會導(dǎo)致FairLock的同步機(jī)制實(shí)現(xiàn)比Lock要稍微慢些。究竟存在多少影響,還依賴于應(yīng)用在FairLock臨界區(qū)執(zhí)行的時長。執(zhí)行時長越大,F(xiàn)airLock帶來的負(fù)擔(dān)影響就越小,當(dāng)然這也和代碼執(zhí)行的頻繁度相關(guān)。

總結(jié)

以上就是本文關(guān)于Java多線程饑餓與公平介紹及代碼示例的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:Java編程之多線程死鎖與線程間通信簡單實(shí)現(xiàn)代碼、解析Java編程之Synchronized鎖住的對象、Java編程redisson實(shí)現(xiàn)分布式鎖代碼示例等,有什么問題可以隨時留言,小編會及時回復(fù)大家的。感謝朋友們對本站的支持!

相關(guān)文章

  • Java調(diào)用opencv實(shí)現(xiàn)圖片矯正功能

    Java調(diào)用opencv實(shí)現(xiàn)圖片矯正功能

    這篇文章主要為大家詳細(xì)介紹了Java如何調(diào)用opencv實(shí)現(xiàn)圖片矯正功能,文中的示例代碼簡潔易懂,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-09-09
  • SpringCloud Alibaba Seata (收藏版)

    SpringCloud Alibaba Seata (收藏版)

    Seata是一款開源的分布式事務(wù)解決方案,致力于在微服務(wù)架構(gòu)在提供高性能和簡單一樣的分布式事務(wù)服務(wù)。這篇文章主要介紹了SpringCloud Alibaba Seata 的相關(guān)知識,需要的朋友可以參考下
    2020-10-10
  • java中JDBC實(shí)現(xiàn)往MySQL插入百萬級數(shù)據(jù)的實(shí)例代碼

    java中JDBC實(shí)現(xiàn)往MySQL插入百萬級數(shù)據(jù)的實(shí)例代碼

    這篇文章主要介紹了java中JDBC實(shí)現(xiàn)往MySQL插入百萬級數(shù)據(jù)的實(shí)例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-01-01
  • SpringBoot2實(shí)現(xiàn)MessageQueue消息隊(duì)列

    SpringBoot2實(shí)現(xiàn)MessageQueue消息隊(duì)列

    本文主要介紹了 SpringBoot2實(shí)現(xiàn)MessageQueue消息隊(duì)列,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Spring的自動裝配Bean的三種方式

    Spring的自動裝配Bean的三種方式

    本篇文章主要介紹了 Spring的自動裝配Bean的三種方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • SpringCache快速使用及入門案例

    SpringCache快速使用及入門案例

    Spring Cache 是Spring 提供的一整套的緩存解決方案,它不是具體的緩存實(shí)現(xiàn),本文主要介紹了SpringCache快速使用及入門案例,感興趣的可以了解一下
    2023-08-08
  • Maven添加Tomcat插件實(shí)現(xiàn)熱部署代碼實(shí)例

    Maven添加Tomcat插件實(shí)現(xiàn)熱部署代碼實(shí)例

    這篇文章主要介紹了Maven添加Tomcat插件實(shí)現(xiàn)熱部署代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-04-04
  • Java的信號量semaphore講解

    Java的信號量semaphore講解

    這篇文章主要介紹了Java的信號量semaphore講解,Semaphore底層是基于AbstractQueuedSynchronizer來實(shí)現(xiàn)的,Semaphore稱為計(jì)數(shù)信號量,它允許n個任務(wù)同時訪問某個資源,需要的朋友可以參考下
    2023-12-12
  • SpringBoot讀取properties文件配置項(xiàng)過程解析

    SpringBoot讀取properties文件配置項(xiàng)過程解析

    這篇文章主要介紹了SpringBoot讀取properties文件配置項(xiàng)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • Java利用POI實(shí)現(xiàn)導(dǎo)入導(dǎo)出Excel表格

    Java利用POI實(shí)現(xiàn)導(dǎo)入導(dǎo)出Excel表格

    這篇文章主要為大家詳細(xì)介紹了Java利用POI實(shí)現(xiàn)導(dǎo)入導(dǎo)出Excel表格,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08

最新評論