(三)线程的并发工具类

Fork-Join

java 下多线程的开发可以我们自己启用多线程,线程池,还可以使用fork-join ,fork-join 可以让我们不去了解诸如Thread,Runnable等相关的知识,只要遵循forkjoin的开发模式,就可以写出很好的多线程并发程序。

分而治之

同时forkjoin在处理某一类问题时非常的有用,哪一类问题?分而治之的问题。十大计算机经典算法:
快速排序、堆排序、归并排序、二分查找、线性查找、深度优先、广度优先、Dijkstra、动态规划、朴素贝叶斯分类,有几个属于分而治之?
3个,快速排序、归并排序、二分查找,还有大数据中M/R都是。
分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同(子问题相互之间有联系就会变为动态规划算法),递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
若将两个有序表合并成一个有序表,称为2-路归并,与之对应的还有多路归并。
对于给定的一组数据,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序后,再用递归方法将排好序的半子表合并成为越来越大的有序序列。
为了提升性能,有时我们在半子表的个数小于某个数(比如15)的情况下,对半子表的排序采用其他排序算法,比如插入排序

image.png

1.先将数组划分为左右两个子表:


image.png
image.png

2.然后继续左右两个子表拆分:


image.png

3.对有序的子表进行排序和比较合并:


image.png

4.对合并后的子表继续比较合并
image.png

几个排序算法的比较

创建数据工具类


/**
 * @author sxylml
 * @Date : 2019/5/17 10:13
 * @Description: 创建数据
 */
public class MakeArray {

    public static int[] makeArray(int arraySize) {
        if (arraySize <= 0) {
            arraySize = 0;
        }
        //new一个随机数发生器
        Random r = new Random();
        int[] result = new int[arraySize];
        for (int i = 0; i < arraySize; i++) {
            //用随机数填充数组
            result[i] = r.nextInt(arraySize * 3);
        }
        return result;
    }


    public static void printArray(int[] array) {

        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]);
            System.out.print(",");
        }
        System.out.println();
    }

}
冒泡排序
public class BubbleSort {

    public static int[] sort(int[] array) {

        for (int i = 0; i < array.length - 1; i++) {
            //-1为了防止溢出
            for (int j = 0; j < array.length - i - 1; j++) {

                if (array[j] > array[j + 1]) {
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
            }
        }
        return array;
    }
}
简单插入排序
public class InsertionSort {
    public static int[] sort(int[] array) {
        if (array.length == 0) {
            return array;
        }
        //当前待排序数据,该元素之前的元素均已被排序过
        int currentValue;
        //在已被排序过数据中倒序寻找合适的位置,如果当前待排序数据比比较的元素要小,将比较的元素元素后移一位
        for (int i = 0; i < array.length - 1; i++) {
            //已被排序数据的索引
            int preIndex = i;
            currentValue = array[preIndex + 1];

            while (preIndex >= 0 && currentValue < array[preIndex]) {
                //将当前元素后移一位
                array[preIndex + 1] = array[preIndex];
                preIndex--;
            }
            //while循环结束时,说明已经找到了当前待排序数据的合适位置,插入
            array[preIndex + 1] = currentValue;
           //   System.out.println("第" + (i + 1) + "次排序");
            //  MakeArray.printArray(array);
        }
        return array;
    }
归并排序
/**
 * @author sxylml
 * @Date : 2019/5/17 10:26
 * @Description: 归并排序
 */
public class MergeSort {



    public static int[] sort(int[] array, int threshold) {

        //如果数组长度小于等于阈值就直接用简单插入排序
        if (array.length <= threshold) {
            return InsertionSort.sort(array);
        } else {
            //切分数组,然后递归调用
            int mid = array.length / 2;
            int[] left = Arrays.copyOfRange(array, 0, mid);
            int[] right = Arrays.copyOfRange(array, mid + 1, array.length);
            return merge(left, right);
        }
    }

    public static int[] merge(int[] left, int[] right) {
        int[] result = new int[left.length + right.length];

        for (int index = 0, i = 0, j = 0; index < result.length; index++) {

            //左边数组已经取完,完全取右边数组的值即可
            if (i >= left.length) {
                result[index] = right[j++];
            } else if (j >= right.length) {
                //右边数组已经取完,完全取左边数组的值即可
                result[index] = left[i++];
            } else if (left[i] > right[j]) {
                //左边数组的元素值大于右边数组,取右边数组的值
                result[index] = right[j];
            } else {
                //右边数组的元素值大于左边数组,取左边数组的值
                result[index] = left[i++];
            }

        }
        return result;
    }
}
测试类
public class SortTest {
    public static void main(String[] args) {
        System.out.println("================归并排序================================");
        long start = System.currentTimeMillis();
        int arraySize = 100000;
        int[] array = MakeArray.makeArray(arraySize);
        array = MergeSort.sort(array, 10);
        System.out.println(" spend time:" + (System.currentTimeMillis() - start) + "ms");


        System.out.println("================直接插入排序============================");
        start = System.currentTimeMillis();
        array = MakeArray.makeArray(arraySize);
        array = InsertionSort.sort(array);
        System.out.println(" spend time:" + (System.currentTimeMillis() - start) + "ms");

        System.out.println("================冒泡插入排序============================");
        start = System.currentTimeMillis();
        array = MakeArray.makeArray(arraySize);
        array = BubbleSort.sort(array);
        System.out.println(" spend time:" + (System.currentTimeMillis() - start) + "ms");


    }
}

几个排序算法的耗时:


image.png

Fork-Join原理

image.png

工作密取

即当前线程的Task已经全被执行完毕,则自动取到其他线程的Task池中取出Task继续执行。
ForkJoinPool中维护着多个线程(一般为CPU核数)在不断地执行Task,每个线程除了执行自己职务内的Task之外,还会根据自己工作线程的闲置情况去获取其他繁忙的工作线程的Task,如此一来就能能够减少线程阻塞或是闲置的时间,提高CPU利用率。


image.png

Fork/Join实战

Fork/Join使用的标准范式

我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork和join的操作机制,通常我们不直接继承ForkjoinTask类,只需要直接继承其子类。

  1. RecursiveAction,用于没有返回结果的任务
  2. RecursiveTask,用于有返回值的任务
    task要通过ForkJoinPool来执行,使用submit 或 invoke 提交,两者的区别是:invoke是同步执行,调用之后需要等待任务完成,才能执行后面的代码;submit是异步执行。
    join()和get方法当任务完成的时候返回计算结果。
image.png

在我们自己实现的compute方法里,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用invokeAll方法时,又会进入compute方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使用join方法会等待子任务执行完并得到其结果。





import tools.SleepTools;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
 * @author sxylml
 * @Date : 2019/5/24 16:03
 * @Description:
 */
public class SumTest {
    public static void main(String[] args) {

        int[] srcArray = MakeArray.makeArray(100000);
        sumforkjoinSum(srcArray);
        sumNormal(srcArray);


    }


    public static void sumforkjoinSum(int[] srcArray) {

        //new 出池的实例
        ForkJoinPool pool = new ForkJoinPool();
        // new 出task 实例
        SumTask innerFind = new SumTask(srcArray, 0, srcArray.length - 1);
        long start = System.currentTimeMillis();
        pool.invoke(innerFind);
        long sum = innerFind.join();
        System.out.println("sumforkjoinSum:The count is " + sum + " spend time:" + (System.currentTimeMillis() - start) + "ms");
    }

    /**
     * ForkJoin执行累加
     */
    private static class SumTask extends RecursiveTask {

        /*阈值*/
        private final int THRESHOLD;
        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;
            this.THRESHOLD = src.length / 10;
        }

        @Override
        protected Long compute() {
            /*任务的大小是否合适*/
            if (toIndex - fromIndex < THRESHOLD) {
//                System.out.println(" from index = "+fromIndex
//                        +" toIndex="+toIndex);
                long count = 0;
                for (int i = fromIndex; i <= toIndex; i++) {
                    //SleepTools.ms(1);
                    count = count + src[i];
                }
                return count;
            } else {
                //fromIndex....mid.....toIndex 拆分子任务
                int mid = (fromIndex + toIndex) / 2;
                SumTask left = new SumTask(src, fromIndex, mid);
                SumTask right = new SumTask(src, mid + 1, toIndex);
                // 提交给 pool 执行
                invokeAll(left, right);
                // 左右两边的值
                return left.join() + right.join();
            }
        }

    }

    /**
     * 普通计算和的方法
     *
     * @param srcArray
     */
    public static void sumNormal(int[] srcArray) {

        long count = 0;
        long start = System.currentTimeMillis();
        for (int i = 0; i < srcArray.length; i++) {
            //休眠下可以看到forkjoin要快
            //SleepTools.ms(1);
            count += srcArray[i];
        }
        System.out.println("sumNormal:The count is " + count + " spend time:" + (System.currentTimeMillis() - start) + "ms");
    }

}

使用fork/join 遍历指定目录(含子目录)找寻指定类型文件


import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

/**
 * @author sxylml
 * @Date : 2019/5/25 09:29
 * @Description: 遍历指定目录(含子目录)找寻指定类型文件
 */
public class FindDirsFiles extends RecursiveAction {
    private File path;
    private String suffix;

    public FindDirsFiles(File path, String suffix) {
        this.path = path;
        this.suffix = suffix;
    }

    @Override
    protected void compute() {
        List subTasks = new ArrayList<>();
        File[] files = path.listFiles();

        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    //对每一个子目录创建一个子任务
                    subTasks.add(new FindDirsFiles(file, suffix));
                } else {
                    //文件,就判断后缀寻找自己需要的
                    if (file.getAbsolutePath().endsWith(suffix)) {
                        System.out.println(file.getAbsolutePath());
                    }
                }

            }

            if (!subTasks.isEmpty()) {
                // 在当前的 ForkJoinPool 上调度所有的子任务。
                for (FindDirsFiles subTask : invokeAll(subTasks)) {
                    subTask.join();
                }
            }
        }
    }

    public static void findDirsFiles(String path, String suffix) {

        try {
            // 用一个 ForkJoinPool 实例调度总任务
            ForkJoinPool pool = new ForkJoinPool();
            FindDirsFiles task = new FindDirsFiles(new File(path), suffix);
            //异步提交
            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) {

        }

    }


    public static void main(String[] args) {
        findDirsFiles("C:/","java");
    }
}

并发工具类

CountDownLatch

闭锁,CountDownLatch 這个类能够使一个线程等待其他线程完成各自的工作后再执行。
例如:应用程序的主线程希望再负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为初始任务的数量。每当完成了一个任务后,计数器的值就会减1
(CountDownLatch.countDown()方法)。当计数器值到达0时,它表示所有的已经完成了任务,然后在闭锁上等待CountDownLatch.await()方法的线程就可以恢复执行任务。

应用场景:

实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了,例如处理excel中多个表单。


image.png
import tools.SleepTools;
import java.util.concurrent.CountDownLatch;
/**
 * @author sxylml
 * @Date : 2019/5/25 10:10
 * @Description: 演示CountDownLatch用法,共5个初始化子线程,6个闭锁扣除点,扣除完毕后,主线程和业务线程才能继续执行
 */
public class UseCountDownLatch {
    /**
     * 可以自己尝试,分别设置 6,大于6 小于6,看运行结果
     */

    static CountDownLatch latch = new CountDownLatch(6);
    /**初始化线程*/
    private static class InitThread implements Runnable {

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

    /**业务线程等待latch的计数器为0完成*/
    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 {
        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........");
    }

}

CyclicBarrier

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrierAction),用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。
CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。


image.png
package com.ch2.forkjoin.tools;

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

/**
 * @author sxylml
 * @Date : 2019/5/25 10:39
 * @Description: 演示CyclicBarrier用法, 共4个子线程,他们全部完成工作后,交出自己结果,
 * 再被统一释放去做自己的事情,而交出的结果被另外的线程拿来拼接字符串
 */
public class UseCyclicBarrier {

    /**
     *
     */
    private static CyclicBarrier barrier = new CyclicBarrier(4, new CollectThread());

    /**
     * 存放子线程工作结果的容器
     */
    private static ConcurrentHashMap resultMap = new ConcurrentHashMap<>();

    /*汇总的任务*/
    private static class CollectThread implements Runnable {

        @Override
        public void run() {
            StringBuilder result = new StringBuilder();
            for (Map.Entry 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);
            try {
                Thread.sleep(1000 + id);
                System.out.println("Thread_" + id + " ....do something ");
                barrier.await();
                Thread.sleep(1000 + id);
                System.out.println("Thread_" + id + " ....do its business ");
                // 可以重复执行汇总
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

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

    }
}

CountDownLatch和CyclicBarrier辨析

CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以反复使用。
CountDownLatch.await一般阻塞工作线程,所有的进行预备工作的线程执行countDown,而CyclicBarrier通过工作线程调用await从而自行阻塞,直到所有工作线程达到指定屏障,再大家一起往下走。
在控制多个线程同时运行上,CountDownLatch可以不限线程数量,而CyclicBarrier是固定线程数。
同时,CyclicBarrier还可以提供一个barrierAction,合并多线程计算结果。

Semaphore

Semaphore (信号量) 是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。应用场景Semaphore 可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。

假如有一个需求。

要读取几十万个文件的数据,因为都是IO密集型任务,我们可以启用几十个线程并发地读取,但是如果读到内存后,还需要存储到数据库中,二数据库的连接数量只有10个,这时我们必须控制只有10个线程同事获取数据库连接保存数据。否则会报错无法获取数据库连接。这个时候,就可以使用Semaphore 来做流量控制。Semaphore 的构造方法Semaphore (int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore 的用法也很简单,首先线程使用Semaphore的acquire() 方法获取一个许可证,使用完之后调用release() 方法归还许可证。还可以用tryAcquire()方法尝试获取许可证。
Semaphore 还提供一些其它方法,如下:
intavailablePermits(): 返回此信号量中当前可用的许可证数。
intgetQuenueLength();返回正在等待获取许可证的线程数。
booleanhasQueuedThreads(); 是否有线程正在等待获取许可证。
void reducePermits(int reduction); 减少reduction 个许可证,是个protected 方法。
Collection getQueuedThreads(); 返回所有等待获取许可证的线程集合,是个protected方法。

image.png
用Semaphore实现数据库连接池

public class SqlConnectImpl implements Connection {
/**
     * 拿一个数据库连接
     */
    public static final Connection fetchConnection() {
        return new SqlConnectImpl();
    }
 // 实现其它方法
}

package com.ch2.forkjoin.tools.semaphore;

import java.sql.Connection;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;

/**
 * @author sxylml
 * @Date : 2019/5/25 11:27
 * @Description: 演示Semaphore用法,一个数据库连接池的实现
 */
public class DBPoolSemaphore {
    /**
     * 连接池大小
     */
    private final static int POOL_SIZE = 10;
    /**
     * 两个指示器,分别表示池子还有可用连接和已用连接
     */
    private final Semaphore useful, useless;

    /**
     * 存放数据库连接的容器
     */
    private static LinkedList pool = new LinkedList();


    //初始化池
    static {
        for (int i = 0; i < POOL_SIZE; i++) {
            pool.addLast(SqlConnectImpl.fetchConnection());
        }
    }

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


    /**
     * 归还连接
     */
    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 connection;
        synchronized (pool) {
            connection = pool.removeFirst();
        }
        useless.release();
        return connection;
    }
}

import tools.SleepTools;
import java.sql.Connection;
import java.util.Random;
/**
 * @author sxylml
 * @Date : 2019/5/25 14:09
 * @Description:
 */
public class AppTest {
    private static DBPoolSemaphore dbPool = new DBPoolSemaphore();

    private static class BusiThread extends Thread {
        @Override
        public void run() {
            /**
             * 让每个线程持有连接的时间不一样
             */
            Random r = new Random();
            long start = System.currentTimeMillis();
            try {
                Connection connect = dbPool.takeConnect();
                System.out.println("Thread_" + Thread.currentThread().getId() + "_获取数据库连接共耗时【" + (System.currentTimeMillis() - start) + "】ms.");
                /**
                 * 模拟业务操作,线程持有连接查询数据
                 */
                SleepTools.ms(100 + r.nextInt(100));
                System.out.println("查询数据完成,归还连接!");
                dbPool.returnConnect(connect);
            } catch (InterruptedException e) {
            }
        }
    }

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

Semaphore注意事项
package com.ch2.forkjoin.tools.semaphore;

import tools.SleepTools;

import java.sql.Connection;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.Semaphore;

/**
 * @author sxylml
 * @Date : 2019/5/25 11:36
 * @Description: 演示Semaphore用法,一个数据库连接池的实现
 */
public class DBPoolNoUseless {

    private final static int POOL_SIZE = 10;
    private final Semaphore useful;
    /**
     * 存放数据库连接的容器
     */
    private static LinkedList pool = new LinkedList();

    /**
     *
     *初始化池
     */
    static {
        for (int i = 0; i < POOL_SIZE; i++) {
            pool.addLast(SqlConnectImpl.fetchConnection());
        }
    }

    public DBPoolNoUseless() {
        this.useful = new Semaphore(10);
    }


    /*归还连接*/
    public void returnConnect(Connection connection) throws InterruptedException {
        if (connection != null) {
            System.out.println("当前有" + useful.getQueueLength() + "个线程等待数据库连接!!"
                    + "可用连接数:" + useful.availablePermits());
            synchronized (pool) {
                pool.addLast(connection);
            }
            useful.release();
        }
    }


    /*从池子拿连接*/
    public Connection takeConnect() throws InterruptedException {
        useful.acquire();
        Connection connection;
        synchronized (pool) {
            connection = pool.removeFirst();
        }
        return connection;
    }

    private static DBPoolNoUseless dbPoolNoUseless = new DBPoolNoUseless();

    private static class BusiThread extends Thread {
        @Override
        public void run() {
            /**
             *让每个线程持有连接的时间不一样
             */

            Random r = new Random();
            long start = System.currentTimeMillis();
            try {
                System.out.println("Thread_" + Thread.currentThread().getId()
                        + "_获取数据库连接共耗时【" + (System.currentTimeMillis() - start) + "】ms.");
                /**
                 * 模拟业务操作,线程持有连接查询数据
                 */
                SleepTools.ms(100 + r.nextInt(100));
                System.out.println("查询数据完成,归还连接!");
                dbPoolNoUseless.returnConnect(new SqlConnectImpl());
            } catch (InterruptedException e) {
            }
        }
    }

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

}

Exchange

Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。


image.png
package com.ch2.forkjoin.tools;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Exchanger;

/**
 * @author sxylml
 * @Date : 2019/5/25 16:04
 * @Description: 演示Exchange用法
 */
public class UseExchange {
    private static final Exchanger> exchange = new Exchanger>();

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                //存放数据的容器
                Set setA = new HashSet();
                try {
                    /*添加数据
                     * set.add(.....)
                     * */

                    setA.add("A");
                    setA.add("a");
                    //交换set
                    setA = exchange.exchange(setA);

                    setA.forEach(src -> System.out.println("setA"+src));
                    /**处理交换后的数据*/
                } catch (InterruptedException e) {
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                /**
                 * 存放数据的容器
                 */
                Set setB = new HashSet();
                try {
                    /*添加数据
                     * set.add(.....)
                     * set.add(.....)
                     * */
                    setB.add("B");
                    setB.add("b");
                    //交换set
                    setB = exchange.exchange(setB);
                    /**处理交换后的数据*/
                    setB.forEach(src -> System.out.println("setB:" + src));
                } catch (InterruptedException e) {
                }
            }
        }).start();

    }
}

image.png

Callable、Future和FutureTask

Runnable是一个接口,在它里面只声明了一个run()方法,由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call(),这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。


image.png

FutureTask类实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。


image.png

因此我们通过一个线程运行Callable,但是Thread不支持构造方法中传递Callable的实例,所以我们需要通过FutureTask把一个Callable包装成Runnable,然后再通过这个FutureTask拿到Callable运行后的返回值。
要new一个FutureTask的实例,有两种方法

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

/**
 * @author sxylml
 * @Date : 2019/5/25 16:58
 * @Description:
 */
public class UseFuture {

    private static class UseCallable implements Callable {
        int sum = 0;

        @Override
        public Integer call() throws Exception {
            System.out.println("Callable子线程开始计算!");

            for (int i = 0; i < 5000; i++) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("Callable子线程计算任务中断!");
                    return null;
                }
                sum = sum + i;
                System.out.println("sum=" + sum);
            }
            System.out.println("Callable子线程计算结束!结果为: " + sum);
            return sum;
        }
    }


    public static void main(String[] args) throws InterruptedException, ExecutionException {

        UseCallable useCallable = new UseCallable();
        //包装
        FutureTask futureTask = new FutureTask<>(useCallable);
        Random r = new Random();
        new Thread(futureTask).start();
        System.out.println("Get UseCallable result = " + futureTask.get());
        futureTask.cancel(true);
    }

}

你可能感兴趣的:((三)线程的并发工具类)