併發編程專題三-JAVA線程的併發工具類

一、Fork-Join框架

1、分而治之

規模爲N的問題,N<閾值,直接解決,N>閾值,將N分解爲K個小規模子問題,子問題互相對立,與原問題形式相同,將子問題的解合併得到原問題的解,像hadoop中的mapreduce,以及二分查找,都是用的分而治之的思想。

動態規範

98cff19cc8292655d9cb7f76af8e19eb466.jpg

2、工作密取 workStealing

就是在任務分割的時候,前面的任務執行可能會比後面的執行速度快,當前面的執行完,後面的還沒執行的時候,執行完前面的任務的線程不會停止,而是從後面的任務的尾部取出子任務繼續工作。Fork-Join就是實現了這樣的機制。

c0998fd2add48858f7affb2e217b1b1f4fe.jpg

Fork/Join使用的標準範式

71b4f575721faaba2fca7c3870169605646.jpg

代碼如下:


import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
 * @Auther: BlackKingW
 * @Date: 2019/4/14 12:09
 * @Description:
 */
public class SumArray {

    private static class SumTask extends RecursiveTask<Integer>{

        private final static int THRESHOLD = MakeArray.ARRAY_LENGTH/10;
        private int[] src; //表示我們要實際統計的數組
        private int fromIndex;//開始統計的下標
        private int toIndex;//統計到哪裏結束的下標

        public SumTask(int[] src, int fromIndex, int toIndex) {
            this.src = src;
            this.fromIndex = fromIndex;
            this.toIndex = toIndex;
        }

		@Override
		protected Integer compute() {
			if(toIndex-fromIndex < THRESHOLD) {
				int count = 0;
				for(int i=fromIndex;i<=toIndex;i++) {
			    	//SleepTools.ms(1);
			    	count = count + src[i];
				}
				return count;
			}else {
				//fromIndex....mid....toIndex
				//1...................70....100
				int mid = (fromIndex+toIndex)/2;
				SumTask left = new SumTask(src,fromIndex,mid);
				SumTask right = new SumTask(src,mid+1,toIndex);
				invokeAll(left,right);
				return left.join()+right.join();
			}
		}
    }


    public static void main(String[] args) {

        ForkJoinPool pool = new ForkJoinPool();
        int[] src = MakeArray.makeArray();

        SumTask innerFind = new SumTask(src,0,src.length-1);

        long start = System.currentTimeMillis();

        pool.invoke(innerFind);//同步調用
        System.out.println("Task is Running.....");

        System.out.println("The count is "+innerFind.join()
                +" spend time:"+(System.currentTimeMillis()-start)+"ms");

    }
}

public class MakeArray {
    //數組長度
    public static final int ARRAY_LENGTH  = 100000000;

    public static int[] makeArray() {

        //new一個隨機數發生器
        Random r = new Random();
        int[] result = new int[ARRAY_LENGTH];
        for(int i=0;i<ARRAY_LENGTH;i++){
            //用隨機數填充數組
            result[i] =  r.nextInt(ARRAY_LENGTH*3);
        }
        return result;

    }
}

二、常用的併發工具類

1、CountDownLatch

作用:是一組線程等待其他的線程完成工作以後在執行,加強版join

await用來等待,countDown負責計數器的減一

代碼示例:


import java.util.concurrent.CountDownLatch;

/**
 * @Auther: BlackKingW
 * @Date: 2019/4/14 12:09
 * @Description:
 */
public class UseCountDownLatch {
	
	static CountDownLatch latch = new CountDownLatch(6);

	//初始化線程(只有一步,有4個)
    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); //休眠1s
                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........");
    }
}

2、CyclicBarrier

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

CyclicBarrier(int parties, Runnable barrierAction),屏障開放,barrierAction定義的任務會執行

代碼示例:

import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;

/**
 * @Auther: BlackKingW
 * @Date: 2019/4/14 12:09
 * @Description:
 */
public class UseCyclicBarrier {
	
	private static CyclicBarrier barrier 
		= new CyclicBarrier(5,new CollectThread());
	
    private static ConcurrentHashMap<String,Long> resultMap
            = new ConcurrentHashMap<>();//存放子線程工作結果的容器

    public static void main(String[] args) {
        for(int i=0;i<=4;i++){
            Thread thread = new Thread(new SubThread());
            thread.start();
        }

    }

    //負責屏障開放以後的工作
    private static class CollectThread implements Runnable{

        @Override
        public void run() {
            StringBuilder result = new StringBuilder();
            for(Map.Entry<String,Long> workResult:resultMap.entrySet()){
            	result.append("["+workResult.getValue()+"]");
            }
            System.out.println(" the result = "+ result);
            System.out.println("do other business........");
        }
    }

    //工作線程
    private static class SubThread implements Runnable{

        @Override
        public void run() {
        	long id = Thread.currentThread().getId();//線程本身的處理結果
            resultMap.put(Thread.currentThread().getId()+"",id);
            Random r = new Random();//隨機決定工作線程的是否睡眠
            try {
                if(r.nextBoolean()) {
                	Thread.sleep(2000+id);
                	System.out.println("Thread_"+id+" ....do something ");
                }
                System.out.println(id+"....is await");
                barrier.await();
            	Thread.sleep(1000+id);
                System.out.println("Thread_"+id+" ....do its business ");
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

CountDownLatch和CyclicBarrier辨析

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

3、Semaphore

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


import java.sql.Connection;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;
/**
 * @Auther: BlackKingW
 * @Date: 2019/4/14 12:09
 * @Description:
 */
public class DBPoolSemaphore {
	
	private final static int POOL_SIZE = 10;
	private final Semaphore useful,useless;//useful表示可用的數據庫連接,useless表示已用的數據庫連接
	
	public DBPoolSemaphore() {
		this. useful = new Semaphore(POOL_SIZE);
		this.useless = new Semaphore(0);
	}
	
	//存放數據庫連接的容器
	private static LinkedList<Connection> pool = new LinkedList<Connection>();
	//初始化池
	static {
        for (int i = 0; i < POOL_SIZE; i++) {
            pool.addLast(SqlConnectImpl.fetchConnection());
        }
	}

	/*歸還連接*/
	public void returnConnect(Connection connection) throws InterruptedException {
		if(connection!=null) {
			System.out.println("當前有"+useful.getQueueLength()+"個線程等待數據庫連接!!"
					+"可用連接數:"+useful.availablePermits());
			useless.acquire();
			synchronized (pool) {
				pool.addLast(connection);
			}	
			useful.release();
		}
	}
	
	/*從池子拿連接*/
	public Connection takeConnect() throws InterruptedException {
		useful.acquire();
		Connection conn;
		synchronized (pool) {
			conn = pool.removeFirst();
		}
		useless.release();
		return conn;
	}
	
}

4、Exchange

兩個線程間的數據交換, 用的比較少

代碼示例:

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Exchanger;
/**
 * @Auther: BlackKingW
 * @Date: 2019/4/14 12:09
 * @Description:
 */
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 {
                	/*添加數據
                	 * set.add(.....)
                	 * */
                	setA = exchange.exchange(setA);//交換set
                	/*處理交換後的數據*/
                } catch (InterruptedException e) {
                }
            }
        }).start();

      //第二個線程
        new Thread(new Runnable() {
            @Override
            public void run() {
            	Set<String> setB = new HashSet<String>();//存放數據的容器
                try {
                	/*添加數據
                	 * set.add(.....)
                	 * set.add(.....)
                	 * */
                	setB = exchange.exchange(setB);//交換set
                	/*處理交換後的數據*/
                } catch (InterruptedException e) {
                }
            }
        }).start();

    }
}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

5、Callable、Future和FutureTask

類之間的關係

isDone,結束,正常還是異常結束,或者自己取消,返回true;

isCancelled 任務完成前被取消,返回true;

cancel(boolean):

  1. 任務還沒開始,返回false
  2. 任務已經啓動,cancel(true),中斷正在運行的任務,中斷成功,返回true,cancel(false),不會去中斷已經運行的任務
  3. 任務已經結束,返回false

代碼示例:



import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;


/**
 * @Auther: BlackKingW
 * @Date: 2019/4/14 12:09
 * @Description:
 */
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);
		}
		
	}

}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

場景舉例:包含圖片和文字的文檔的處理:圖片(雲上),可以用future去取圖片,主線程繼續解析文字。

發佈了42 篇原創文章 · 獲贊 102 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章