JAVA線程併發工具類筆記整理Fork-Join、CountDownLatch、CyclicBarrier、Semaphore、Exchange、Callable、FutureTask

1. Fork-Join

什麼是分而治之
規模爲N的問題,N<閾值,直接解決,N>閾值,將N分解爲K個小規模子問題,子問題互相對立,與原問題形式相同,將子問題的解合併得到原問題的解。分而治之思想應用:在快速排序、歸併排序、動態規劃等等。
在這裏插入圖片描述

工作密取(workStealing):線程A完成了工作之後可以被調度去幫線程B完成task,爲了避免競爭獲取任務,從相反的方向獲取任務。
在這裏插入圖片描述

Fork/Join使用的標準範式:

在這裏插入圖片描述
使用:
1.創建RecursiveTask、RecursiveAction的實現類對象myTask
2.ForkJoinPool pool = new ForkJoinPool();
3.調用
pool.invoke(myTask); // 同步調用
pool.execute(myTask); // 異步異步

繼承RecursiveTask類,重寫compute方法

帶返回結果值

案例:統計整形數組中所有元素的和

繼承RecursiveAction類,重寫compute方法

無返回值

案例:遍歷文件夾尋找指定類型文件

public class FindDirsFiles extends RecursiveAction{

    private File path;//當前任務需要搜尋的目錄
    public FindDirsFiles(File path) {
        this.path = path;
    }

	@Override
	protected void compute() {
		//任務列表
		List<FindDirsFiles> subTasks = new ArrayList<>();
		File[] files = path.listFiles();
		if(files!=null) {
			for(File file:files) {
				//不是文件則放進任務列表裏
				if(file.isDirectory()) {
					subTasks.add(new FindDirsFiles(file));
				}else {
					//遇到文件,檢查
					if(file.getAbsolutePath().endsWith("txt")) {
						System.out.println("文件:"+file.getAbsolutePath());
					}
				}
			}
			if(!subTasks.isEmpty()) {
				//把任務列表丟進去同步執行
				for(FindDirsFiles subTask:invokeAll(subTasks)) {
					subTask.join();//等待子任務執行完成
				}
			}
		}

	}


    public static void main(String [] args){
        try {
            // 用一個 ForkJoinPool 實例調度總任務
            ForkJoinPool pool = new ForkJoinPool();
            FindDirsFiles task = new FindDirsFiles(new File("D:/"));

            pool.execute(task);//異步調用

            System.out.println("Task is Running......");
            Thread.sleep(1);
            int otherWork = 0;
            for(int i=0;i<100;i++){
                otherWork = otherWork+i;
            }
            System.out.println("Main Thread done sth......,otherWork="+otherWork);
            task.join();//阻塞的方法
            System.out.println("Task end");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2. CountDownLatch

作用:是一組線程等待其他的線程完成工作以後在執行,加強版join
await() 用來等待,countDown() 負責計數器的減一
案例實現:主線程業務線程 等待 初始化線程 完成後繼續自己的工作

public class UseCountDownLatch {
	
	static CountDownLatch latch = new CountDownLatch(6);

	//初始化線程(後面主線程會new四個實例出來)
    private static class InitThread implements Runnable{
        @Override
        public void run() {
        	System.out.println("Thread_"+Thread.currentThread().getId()
        			+" ready init work......");
        	latch.countDown();//初始化線程完成工作了,countDown方法只扣減一次;
            for(int i =0;i<2;i++) {
            	System.out.println("Thread_"+Thread.currentThread().getId()
            			+" ........continue do its work");
            }
        }
    }
    
    //業務線程(等待準備完畢後執行)
    private static class BusiThread implements Runnable{
        @Override
        public void run() {
        	try {
				latch.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
            for(int i =0;i<3;i++) {
            	System.out.println("BusiThread_"+Thread.currentThread().getId()
            			+" do business-----");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
    	//單獨的初始化線程,初始化分爲2步,需要扣減兩次
        new Thread(new Runnable() {
            @Override
            public void run() {
            	SleepTools.ms(1);
                System.out.println("Thread_"+Thread.currentThread().getId()
            			+" ready init work step 1st......");
                latch.countDown();//每完成一步初始化工作,扣減一次
                System.out.println("begin step 2nd.......");
                SleepTools.ms(1);
                System.out.println("Thread_"+Thread.currentThread().getId()
            			+" ready init work step 2nd......");
                latch.countDown();//每完成一步初始化工作,扣減一次
            }
        }).start();
        new Thread(new BusiThread()).start();
        for(int i=0;i<=3;i++){
            Thread thread = new Thread(new InitThread());
            thread.start();
        }

        latch.await();
        System.out.println("Main do ites work........");
    }
}

SleepTools 代碼:

public class SleepTools {
	
	/**
	 * 按秒休眠
	 * @param seconds 秒數
	 */
    public static final void second(int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
        }
    }
    
    /**
     * 按毫秒數休眠
     * @param seconds 毫秒數
     */
    public static final void ms(int seconds) {
        try {
            TimeUnit.MILLISECONDS.sleep(seconds);
        } catch (InterruptedException e) {
        }
    }
}

3.CyclicBarrier

CyclicBarrier(int parties) 讓一組線程達到某個屏障,被阻塞,一直到組內最後一個線程達到屏障時,屏障開放,所有被阻塞的線程會繼續運行。
CyclicBarrier(int parties, Runnable barrierAction) ,屏障開放,barrierAction定義的任務會執行。

//創建
CyclicBarrier barrier = new CyclicBarrier(5);
//等待
barrier.await();

/**
* 線程執行到await()方法處會阻塞,當有五個線程都執行到await(),
* 它們會一起繼續執行後面的代碼。
**/

CountDownLatch和CyclicBarrier辨析
1、countdownlatch放行由第三者控制,CyclicBarrier放行由一組線程本身控制
2、countdownlatch放行條件>=線程數,CyclicBarrier放行條件=線程數

舉個簡單的栗子
CountDownLatch:小明和小紅和小華一起出去自駕遊,

//等兩個人-小明、小紅   latch.await() //等着 )
CountDownLatch latch = new CountDownLatch(2) ;

小華啓動了車子,等着大家搬好東西上車,小明上車後說我準備好了,

latch.countDown();

小紅上車後說我準備好了,

latch.countDown();

(latch值爲0了)就可以出發咯。

CyclicBarrier:小明和小紅和小華一起去旅遊,相約在火車站見面,,先到的等着,三人到齊一起出發。

4. Semaphore

控制同時訪問某個特定資源的線程數量,常用於流量控制

Semaphore sm = new Semaphore(10) ;//大小爲10
sm.acquire();//拿一個 -1
sm.release();//釋放一個 +1

4. Exchange

兩個線程間的數據交換, 因爲只能適用於兩個線程間的交換,使用較少。
示例:

public class UseExchange {
    private static final Exchanger<Set<String>> exchange 
    	= new Exchanger<Set<String>>();

    public static void main(String[] args) {

    	//第一個線程
        new Thread(new Runnable() {
            @Override
            public void run() {
            	Set<String> setA = new HashSet<String>();//存放數據的容器
                try {
                	//添加數據
                    setA.add("A");
                	setA = exchange.exchange(setA);//交換set
                	//處理交換後的數據
                    for (String s : setA){
                        System.out.println("我是線程A,我的值爲:"+ s);
                    }
                } catch (InterruptedException e) {
                }
            }
        }).start();

      //第二個線程
        new Thread(new Runnable() {
            @Override
            public void run() {
            	Set<String> setB = new HashSet<String>();//存放數據的容器
                try {
                	//添加數據
                	setB.add("B");
                	setB = exchange.exchange(setB);//交換set
                	//處理交換後的數據
                    for (String s : setB){
                        System.out.println("我是線程B,我的值爲:"+ s);
                    }

                } catch (InterruptedException e) {
                }
            }
        }).start();

    }
}

5. Callable、Future和FutureTask

isDone() ,結束,正常還是異常結束,或者自己取消,返回true;
isCancelled() 任務完成前被取消,返回true;
cancel(boolean)
1、 任務還沒開始,返回false
2、 任務已經啓動,cancel(true),中斷正在運行的任務,中斷成功,返回true,cancel(false),不會去中斷已經運行的任務
3、 任務已經結束,返回false

包含圖片和文字的文檔的處理:圖片(雲上),可以用future去取圖片,主線程繼續解析文字。
案例:使用子線程計算求和後返回給主線程結果,並測試中斷方法:

public class UseFuture {
	
	/*實現Callable接口,允許有返回值*/
	private static class UseCallable implements Callable<Integer>{

		private int sum;
		@Override
		public Integer call() throws Exception {
			System.out.println("Callable子線程開始計算");
			Thread.sleep(2000);//這個睡眠會響應中斷,拋出異常讓線程停止,否則沒有處理中斷,線程不會管別人是否讓他中斷
			for(int i=0;i<5000;i++) {
				sum = sum+i;
			}
			System.out.println("Callable子線程計算完成,結果="+sum);
			return sum;
		}

	}
	
	public static void main(String[] args) 
			throws InterruptedException, ExecutionException {
		
		UseCallable useCallable = new UseCallable();
		FutureTask<Integer> futureTask = new FutureTask<Integer>(useCallable);
		new Thread(futureTask).start();
		Random r = new Random();
		SleepTools.second(1);
		if(r.nextBoolean()) {//隨機決定是獲得結果還是終止任務
			System.out.println("Get UseCallable result = "+futureTask.get());
		}else {
			System.out.println("中斷計算");
			futureTask.cancel(true);
		}
		
	}

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