Java多線程知識點整理(CyclicBarrier、CountDownLatch、Callable、Future和FutureTask)

1、CyclicBarrierjava

    CyclicBarrier從字面理解是指循環屏障,它能夠協同多個線程,讓多個線程在這個屏障前等待,直到全部線程都達到了這個屏障時,再一塊兒繼續執行後面的動做。安全

代碼:多線程

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierThread extends Thread{
	
	private CyclicBarrier cb;
	
	private int second;
	
	
	public CyclicBarrierThread(CyclicBarrier cb, int second) {
		
		this.cb = cb;
		this.second = second;
	}


	@Override
	public void run() {
		try {
			System.out.println("開始了!");
			Thread.sleep(second*1000);
			System.out.println("準備等待!"+System.currentTimeMillis());
			cb.await();
			System.out.println("結束了");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				System.out.println("CyclicBarrier的全部線程await()結束了,我運行了, 時間爲"+System.currentTimeMillis());
				
			}
		};
		 	CyclicBarrier cb = new CyclicBarrier(3, runnable);
		    CyclicBarrierThread cbt0 = new CyclicBarrierThread(cb, 3);
		    CyclicBarrierThread cbt1 = new CyclicBarrierThread(cb, 6);
		    CyclicBarrierThread cbt2 = new CyclicBarrierThread(cb, 9);
		    cbt0.start();
		    cbt1.start();
		    cbt2.start();
		
	}
	
}

運行的結果:異步

從代碼的結果來看,先運行run方法,遇到await()以後,睡眠,而後在運行runnable的方法,而後再運行run方法進行結束。ide

從使用來看,可能有人以爲CyclicBarrier和CountDownLatch有點像,都是多個線程等待相互完成以後,再執行後面的代碼。實際上,CountDownLatch和CyclicBarrier都是用於多個線程間的協調的,它們兩者的幾個差異是:this

一、CountDownLatch是在多個線程都進行了latch.countDown()後纔會觸發事件,喚醒await()在latch上的線程,而執行countDown()的線程,執行完countDown()後會繼續本身線程的工做;CyclicBarrier是一個柵欄,用於同步全部調用await()方法的線程,而且等全部線程都到了await()方法時,這些線程才一塊兒返回繼續各自的工做。spa

二、另外CountDownLatch和CyclicBarrier的一個差異是,CountDownLatch不能循環使用,計數器減爲0就減爲0了,不能被重置,CyclicBarrier能夠循環使用。線程

三、CountDownLatch能夠喚起多條線程的任務,CyclicBarrier只能喚起一條線程的任務。設計

注意,由於使用CyclicBarrier的線程都會阻塞在await方法上,因此在線程池中使用CyclicBarrier時要特別當心,若是線程池的線程過少,那麼就會發生死鎖了。code

2、CountDownLatch

    CountDownLatch主要提供的機制是當多個(具體數量等於初始化CountDownLatch時count參數的值)線程都達到了預期狀態或完成預期工做時觸發事件,其餘線程能夠等待這個事件來觸發本身的後續工做。值得注意的是,CountDownLatch是能夠喚醒多個等待的線程的。到達本身預期狀態的線程會調用CountDownLatch的countDown方法,等待的線程會調用CountDownLatch的await方法。若是CountDownLatch初始化的count值爲1,那麼這就退化爲一個單一事件了,便是由一個線程來通知其餘線程,效果等同於對象的wait和notifyAll,count值大於1是經常使用的方式,目的是爲了讓多個線程到達各自的預期狀態,變爲一個事件進行通知,線程則繼續本身的行爲。

import java.util.concurrent.CountDownLatch;

public class WorkThread extends Thread{
	
	private CountDownLatch cdl;
    private int sleepSecond;
        
    public WorkThread(String name, CountDownLatch cdl, int sleepSecond)
    {
        super(name);
        this.cdl = cdl;
        this.sleepSecond = sleepSecond;
    }
     
    @Override
    public void run()
    {
        try
        {
            System.out.println(this.getName() + "啓動了,時間爲" + System.currentTimeMillis());
            Thread.sleep(sleepSecond * 1000);
            cdl.countDown();
            System.out.println(this.getName() + "執行完了,時間爲" + System.currentTimeMillis());
        } 
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
    private static class DoneThread extends Thread
    {
        private CountDownLatch cdl;
            
        public DoneThread(String name, CountDownLatch cdl)
        {
            super(name);
            this.cdl = cdl;
        }
            
        public void run()
        {
            try
            {
                System.out.println(this.getName() + "要等待了, 時間爲" + System.currentTimeMillis());
                cdl.await();
                System.out.println(this.getName() + "等待完了, 時間爲" + System.currentTimeMillis());
            } 
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
        
    public static void main(String[] args) throws Exception
    {
        CountDownLatch cdl = new CountDownLatch(3);
        DoneThread dt0 = new DoneThread("DoneThread1", cdl);
        //DoneThread dt1 = new DoneThread("DoneThread2", cdl);
        dt0.start();
        //dt1.start();
        WorkThread wt0 = new WorkThread("WorkThread1", cdl, 2);
        WorkThread wt1 = new WorkThread("WorkThread2", cdl, 3);
        WorkThread wt2 = new WorkThread("WorkThread3", cdl, 4);
        wt0.start();
        wt1.start();
        wt2.start();
    }
	
	
	
	
}

運行結果:

這至關因而一種進化版本的等待/通知機制,它能夠的實現的是多個工做線程完成任務後通知多個等待線程開始工做,以前的都是一個工做線程完成任務通知一個等待線程或者一個工做線程完成任務通知全部等待線程。

CountDownLatch實際上是頗有用的,特別適合這種將一個問題分割成N個部分的場景,全部子部分完成後,通知別的一個/幾個線程開始工做。好比我要統計C、D、E、F盤的文件,能夠開4個線程,分別統計C、D、E、F盤的文件,統計完成把文件信息彙總到另外一個/幾個線程中進行處理。

3、Callable、Future和FutureTask

3.一、Callable

Callable和rRunnable差很少,二者都是爲那些其實例可能被另外一個線程執行的類而設計的,最主要的差異在於Runnable不會返回線程運算結果,Callable能夠(假如線程須要返回運行結果)。

3.二、Future

Future是一個接口表示異步計算的結果,它提供了檢查計算是否完成的方法,以等待計算的完成,並獲取計算的結果。Future提供了get()、cancel()、isCancel()、isDone()四種方法,表示Future有三種功能:

一、判斷任務是否完成

二、中斷任務

三、獲取任務執行結果

3.三、FutureTask

FutureTask是Future的實現類,它提供了對Future的基本實現。可以使用FutureTask包裝Callable或Runnable對象,由於FutureTask實現了Runnable,因此也能夠將FutureTask提交給Executor。

使用方法

Callable、Future、FutureTask通常都是和線程池配合使用的,由於線程池ThreadPoolExecutor的父類AbstractExecutorService提供了三種submit方法:

一、public Future<?> subit(Runnable task){...}

二、public <T> Future<T> submit<Runnable task, T result>{...}

三、public <T> Future<T> submit<Callable<T> task>{...}

第2個用得很少,第1個和第3個比較有用。

代碼:Callable和Future

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableThread implements Callable<String>{

	@Override
	public String call() throws Exception {
		System.out.println("開始了call方法"+System.currentTimeMillis());
		Thread.sleep(1000);
		return "sucess";
	}
	
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		 ExecutorService es = Executors.newFixedThreadPool(1);
		 
		 CallableThread callableThread = new CallableThread();
		 
		 Future<String> submit = es.submit(callableThread);
		 es.shutdown();
		 try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		 System.out.println("mian線程等待!");
		 String string = submit.get();
		 System.out.println(string);
	}
}

運行結果:

Callable+FutureTask使用示例:

public static class CallableThread implements Callable<String>
{
    public String call() throws Exception
    {
        System.out.println("進入CallableThread的call()方法, 開始睡覺, 睡覺時間爲" + System.currentTimeMillis());
        Thread.sleep(10000);
        return "123";
    }
}
    
public static void main(String[] args) throws Exception
{
    ExecutorService es = Executors.newCachedThreadPool();
    CallableThread ct = new CallableThread();
    FutureTask<String> f = new FutureTask<String>(ct);
    es.submit(f);
    es.shutdown();
        
    Thread.sleep(5000);
    System.out.println("主線程等待5秒, 當前時間爲" + System.currentTimeMillis());
        
    String str = f.get();
    System.out.println("Future已拿到數據, str = " + str + ", 當前時間爲" + System.currentTimeMillis());
}

總結:使用Callable、Future和FutureTask的好處

上面演示了兩個例子,其實反映的是現實中一種狀況,把上面的例子稍微擴展一下就是:

有一個method()方法,方法中執行方法A返回一個數據要10秒鐘,A方法後面的代碼一共要執行20秒鐘,可是這20秒的代碼中有10秒的方法並不依賴方法A的執行結果,有10秒鐘的代碼依賴方法A的執行結果。此時若採用同步的方式,那麼勢必要先等待10秒鐘,等待方法A執行完畢,返回數據,再執行後面20秒的代碼。

不得不說這是一種低效率的作法。有了Callable、Future和FutureTask,那麼:

一、先把A方法的內容放到Callable實現類的call()方法中

二、method()方法中,Callable實現類傳入Executor的submit方法中

三、執行後面方法中10秒不依賴方法A運行結果的代碼

四、獲取方法A的運行結果,執行後面方法中10秒依賴方法A運行結果的代碼

這樣代碼執行效率一會兒就提升了,程序沒必要卡在A方法處。異步執行代碼。

固然,也能夠不用Callable,採用實現Runnable的方式,run()方法執行完了想個辦法給method()方法中的某個變量V賦個值就行了。可是我上一篇文章開頭就說了,之因此要用多線程組件,就是由於JDK幫咱們很好地實現好了代碼細節,讓開發者更多能夠關注業務層的邏輯。若是使用Runnable的方式,那麼咱們本身就要考慮不少細節,好比Runnable實現類的run()方法執行完畢給V賦值是否線程安全、10秒後若是A方法沒有執行完致使V尚未值怎麼辦,況且JDK還給用戶提供了取消任務、判斷任務是否存在等方法。既然JDK已經幫咱們考慮並實現這些細節了,在沒有有說服力的理由的狀況下,咱們爲何還要本身寫run()方法的實現呢? 

相關文章
相關標籤/搜索