Java并发-并发工具类

136 阅读9分钟

fork-join

分而治之

规模为N的问题,N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解

动态规化

与分而治之基本相当,最大的区别在于哥哥怎么看之间是相互关联的

**

工作密取

若线程a已完成工作,线程b未完成工作,线程a后重线程b执行任务的尾部帮助执行,这样会减少竞争的概率

image-20200414160533468

fork-join的标准模式

image-20200414160615626

RecursiveTask()有返回值是调用,RescursiveAction()无返回值时调用。

同步执行是Pool.invoke(),异步执行是Pool.execute()

Forkjoin 不一定比单线程块,应为forkjoin会上下文切换

下面有两个例子:

  • Fork/Join的同步用法同时演示返回结果值:统计整形数组中所有元素的和,为模拟其他业务操作,我们在计算的时候sleep1ms

    MakeArray

    public class MakeArray {
    
        public static final int ARRAY_LENGTH = 4000;
    
        public static int[] makeArray(){
            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;
        }
    }
    

    SumNormal

    public class SumNormal {
        public static void main(String[] args) {
            int count = 0;
            int[] src = MakeArray.makeArray();
    
            long start = System.currentTimeMillis();
            for(int i= 0;i<src.length;i++){
                SleepTools.ms(1);
                count = count + src[i];
            }
            System.out.println("The count is "+count
                    +" spend time:"+(System.currentTimeMillis()-start)+"ms");
        }
    }
    
    //输出The count is 23815133 spend time:5062ms
    

    SumFork

    public class SumFork {
    
        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{
                    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 forkJoinPool = new ForkJoinPool();
            int[] src = MakeArray.makeArray();
            SumTask sumTask = new SumTask(src, 0, src.length - 1);
            long start = System.currentTimeMillis();
    
            forkJoinPool.invoke(sumTask);
            System.out.println("Task is running");
    
            System.out.println("The count is "+sumTask.join()+" spend time:"+(System.currentTimeMillis()-start)+" ms");
    
    
        }
    }
    //输出
    //Task is running
    //The count is 23940852 spend time:620 ms
    
  • Fork/Join的异步用法同时演示不要求返回值:遍历指定目录(含子目录)寻找指定类型文件

    public class FindDirsFiles  extends RecursiveAction {
        private File path;
    
        public FindDirsFiles(File path) {
            this.path = path;
        }
    
        public static void main(String[] args) throws InterruptedException {
            ForkJoinPool forkJoinPool = new ForkJoinPool();
            FindDirsFiles task = new FindDirsFiles(new File("/Users/sa/Pictures"));
    
            forkJoinPool.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);
            Thread.sleep(1000000);//阻塞的方法
    //        task.join();//阻塞的方法
            System.out.println("Task end");
        }
    
    
        @Override
        protected void compute() {
    
            ArrayList<FindDirsFiles> subTasks = new ArrayList<>();
    
            File[] files = path.listFiles();
            if(null!=files){
                for (File file : files){
                    if(file.isDirectory()){
                        subTasks.add(new FindDirsFiles(file));
                    }else{
                        if(file.getAbsolutePath().endsWith("jpg")){
                            System.out.println("文件:"+file.getAbsolutePath());
                        }
                    }
                }
                if (!subTasks.isEmpty()){
                    for (FindDirsFiles subTask:invokeAll(subTasks)){
                        subTask.join();
                    }
                }
            }
        }
    }
    
    

    输出结果如下:

    Task is running
    文件:/Users/sa/Pictures/pap.er/JtvPWb0QSOc.jpg
    Main Thread done sth......,otherWork=4950
    文件:/Users/sa/Pictures/pap.er/ZdPbCLXTZ0E.jpg
    文件:/Users/sa/Pictures/pap.er/3TiRE8cerXo.jpg
    文件:/Users/sa/Pictures/pap.er/3RicCdnXfHs.jpg
    文件:/Users/sa/Pictures/pap.er/ppj96EOQRkA.jpg
    文件:/Users/sa/Pictures/pap.er/W9L8a0B2kV4.jpg
    

CountDownLatch

作用:是一组线程等待其他的线程完成工作以后再执行,加强版join

await用来等待,countDown负责计数器的减一

演示CountDownLatch,有5个初始化的线程,6个扣除点,扣除完毕以后,主线程和业务线程才能继续自己的工作

使用如下:

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

    private static class InitThread implements Runnable{

        @Override
        public void run() {
            System.out.println("Thread "+Thread.currentThread().getId()+" ready to work");
            latch.countDown();
            for(int i = 0;i<2;i++){
                System.out.println("Thread "+Thread.currentThread().getId()+"........continue to work");
            }
        }
    }

    private static class BusinessThread implements Runnable{

        @Override
        public void run() {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for(int i=0;i<3;i++){
                System.out.println("BusinessThread "+Thread.currentThread().getId()+"....do business work");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                SleepTools.ms(1);
                System.out.println("Thread "+Thread.currentThread().getId()+" ready to work 1st step");
                latch.countDown();
                System.out.println("begin 2nd step");
                SleepTools.ms(1);
                System.out.println("Thread "+Thread.currentThread().getId()+" ready to work 1st step");
                latch.countDown();
            }
        }).start();

        new Thread(new BusinessThread()).start();

        for (int i=0;i<=3;i++){
            new Thread(new InitThread()).start();
        }
        latch.await();
        System.out.println("main thread work");
    }
}

输出结果为为

Thread 13 ready to work
Thread 16 ready to work
Thread 15 ready to work
Thread 14 ready to work
Thread 15........continue to work
Thread 16........continue to work
Thread 13........continue to work
Thread 13........continue to work
Thread 16........continue to work
Thread 15........continue to work
Thread 14........continue to work
Thread 14........continue to work
Thread 11 ready to work 1st step
begin 2nd step
Thread 11 ready to work 1st step
main thread work
BusinessThread 12....do business work
BusinessThread 12....do business work
BusinessThread 12....do business work

CyslicBarrier

让一组线程到达某个屏障 被阻塞,知道所有线程都到达屏障时,才释放屏障,所有被阻塞的改组线程才会继续运行。有两个构造函数

  • CyclicBarrier(int parties)

  • CyclicBarrier(int parties,Runnable barrierAction),在屏障释放之后barrierAction这个线程会执行

    public class UseCyclicBarrier {
    
        private static CyclicBarrier barrier  = new CyclicBarrier(5, new CollectionThread());
    
    
        private static ConcurrentHashMap<String,Long>  hashMap = new ConcurrentHashMap<>();
    
        private static class CollectionThread implements Runnable{
    
            @Override
            public void run() {
                StringBuilder b = new StringBuilder();
                for (Map.Entry<String,Long> workMap:hashMap.entrySet()){
                    b.append("["+workMap.getValue()+"]");
                }
                System.out.println("the result :"+b);
                System.out.println("do other business");
            }
        }
    
        private static class WorkThread implements Runnable{
    
            @Override
            public void run() {
                long id = Thread.currentThread().getId();
                hashMap.put(id+"",id);
                Random r = new Random();
                try {
                    if(r.nextBoolean()){
                        Thread.sleep(2000+id);
                        System.out.println("Thread "+ id+"run something");
    
                    }
                    System.out.println(id+".......is await");
                    barrier.await();
                    Thread.sleep(1000+id);
                    System.out.println("Thread "+id+"do it's work");
    
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args){
            for (int i = 0;i<=4;i++){
                new Thread(new WorkThread()).start();
            }
        }
    }
    

    输出

    15.......is await
    13.......is await
    12.......is await
    Thread 11run something
    11.......is await
    Thread 14run something
    14.......is await
    the result :[11][12][13][14][15]
    do other business
    Thread 11do it's work
    Thread 12do it's work
    Thread 13do it's work
    Thread 14do it's work
    Thread 15do it's work
    

CountDownLatch与 CyclicBarrier 辨析

  • CountDownLatch放行由第三者控制,CyclicBarrier有一组线程本身控制
  • CountDownLatch一帮的放行条件为 扣除点>线程数,CyclicBarrier放行条件为扣除点 = 线程数
  • 使用场景不同
    • CountDownLatch在countdown之后还是可以继续执行本线程的
    • CyclicBarrier在await之后,是必须等到所有线程执行到这步,才能继续执行线程下下一步

Semaphore

信号量,控制同时访问某个特定资源的线程数量,用在流量控制。下面我们使用Semaphore实现我们之前的连接池

实现一个Connection 为SqlConnectionImpl

public class SqlConnectionImpl implements Connection {

    /*拿一个数据库连接*/
    public static final Connection fetchConnection(){
        return new SqlConnectImpl();
    }

    @Override
    public Statement createStatement() throws SQLException {
        return null;
    }
    ...  
}

使用SemaPhore编写一个链接池

public class DBPoolSemaphore {
    private static final int SIZE = 10;
    private final Semaphore useful, useless;

    public DBPoolSemaphore() {
        this.useful = new Semaphore(SIZE);
        this.useless = new Semaphore(0);
    }

    private static LinkedList<Connection> pool = new LinkedList<>();

    static {
        for (int i = 0; i < SIZE; i++) {
            pool.add(SqlConnectImpl.fetchConnection());
        }
    }

    public void releaseConn(Connection conn) throws InterruptedException {
        if (null != conn) {
            useless.acquire();
            System.out.println("当前有" + useful.getQueueLength() + "个线程等待链接数据哭," +
                    "可用链接" + useful.availablePermits());
            synchronized (pool) {
                pool.addLast(conn);
            }
            useful.release();
        }
    }

    public Connection fetchConn() throws InterruptedException {
        useful.acquire();
        Connection connection;
        synchronized (pool) {
            connection = pool.removeFirst();
        }
        useless.release();
        return connection;
    }

}

编写一个测试

public class DBPoolTest {
   private static DBPoolSemaphore poolSemaPhore = new DBPoolSemaphore();

   private static class BusinessThread implements Runnable{

       @Override
       public void run() {
           Random r = new Random();

           long start = System.currentTimeMillis();
           try{
               Connection connection = poolSemaPhore.fetchConn();
               System.out.println("Thread "+Thread.currentThread().getId()+"获取数据库链接耗时"+(System.currentTimeMillis()-start)+"ms");
               SleepTools.ms(100+r.nextInt(100));
               System.out.println("查询完成归还链接");
               poolSemaPhore.releaseConn(connection);

           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
   }
   public static void main(String[] args){
       for (int i=0;i<50;i++){
           new Thread(new BusinessThread()).start();
       }
   }
}

输出结果如下

Thread 11获取数据库链接耗时0ms
Thread 12获取数据库链接耗时0ms
Thread 18获取数据库链接耗时0ms
....
Thread 19获取数据库链接耗时0ms
查询完成归还链接
查询完成归还链接
当前有40个线程等待链接数据哭,可用链接0
当前有40个线程等待链接数据哭,可用链接0
Thread 21获取数据库链接耗时131ms
Thread 22获取数据库链接耗时131ms
查询完成归还链接
当前有38个线程等待链接数据哭,可用链接0
...
查询完成归还链接
当前有2个线程等待链接数据哭,可用链接0
Thread 59获取数据库链接耗时637ms
查询完成归还链接
当前有1个线程等待链接数据哭,可用链接0
Thread 60获取数据库链接耗时678ms
查询完成归还链接
当前有0个线程等待链接数据哭,可用链接0
查询完成归还链接
...
当前有0个线程等待链接数据哭,可用链接8
查询完成归还链接
当前有0个线程等待链接数据哭,可用链接9

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 {
                	/*添加数据
                	 * 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();

    }
}

Callable、Future和FutureTask

image-20200415112611105

isDone,结束,正常还是异常结束,或者自己取消,返回true;

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

cancel(boolean):

1、 任务还没开始,返回false

2、 任务已经启动,cancel(true),中断正在运行的任务,中断成功,返回true,cancel(false),不会去中断已经运行的任务

3、 任务已经结束,返回false

使用场景包含图片和文字的文档的处理:图片(云上),可以用future去取图片,主线程继续解析文

public class UseFuture {

    public static class UseCallable implements Callable<Integer>{

        private int sum;
        @Override
        public Integer call() throws InterruptedException {
            System.out.println("Callable 子线程开始计算");
            System.out.println();
            while(!Thread.currentThread().isInterrupted()){
                for(int i=0;i<5000;i++) {
                    sum = sum+i;
                }
                System.out.println("Callable子线程计算完成,结果="+sum);

            }
            System.out.println(Thread.currentThread().getName()+" "+Thread.currentThread().isInterrupted());
            return sum;
        }
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        UseCallable useCallable = new UseCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(useCallable);
        new Thread(futureTask).start();
        Random r = new Random();
        if(r.nextBoolean()){
            System.out.println("Get UseCallable result = "+futureTask.get());
        }else{
            System.out.println("中断计算开始");
            futureTask.cancel(true);
        }
    }
}

输出

Callable 子线程开始计算

中断计算开始
Callable子线程计算完成,结果=12497500
Thread-0 true

或者

Callable子线程计算完成,结果=-1578195668
Callable子线程计算完成,结果=-1565698168
Callable子线程计算完成,结果=-1553200668
Callable子线程计算完成,结果=-1540703168
Callable子线程计算完成,结果=-1528205668
Callable子线程计算完成,结果=-1515708168
Callable子线程计算完成,结果=-1503210668
Callable子线程计算完成,结果=-1490713168
Callable子线程计算完成,结果=-1478215668
Callable子线程计算完成,结果=-1465718168
...