*java并发编程:2,线程的并发工具类

学习路径:基础入门-》初步应用-》高级-》源码分析

并发编程学习目录:
1,线程基础、线程之间的共享和协作
2,线程的并发工具类
3,原子操作CAS
4,显示锁和AQS
5,并发容器
6,线程池和Exector框架
7,线程安全
8a,实战项目-并发任务执行框架
8b,实战项目-性能优化实战
9,JMM和底层原理
10,java8新增的并发

*java并发编程:2,线程的并发工具类

1,Fork-Join

1.1 Fork/Join 体现了“分而治之”

什么是分而治之?

  • 设计思想
  • 策略

十大计算机经典算法:
*快速排序、
堆排序、
*归并排序、
*二分查找、
线性查找、
深度优化、
广度优化、
Dijkstra、
动态规划:
朴素贝叶斯分类,
有几种属于分而治之?(打※号的属于)

1.2 Fork-Join原理:

*java并发编程:2,线程的并发工具类_第1张图片
Fork-Join 的执行流程:
*java并发编程:2,线程的并发工具类_第2张图片
*java并发编程:2,线程的并发工具类_第3张图片
类的继承结构图如下:

  • ForkJoinPool 实现了线程池接口
  • RecursiveTask 和 RecursiveAction 都实现了 Future 接口
    *java并发编程:2,线程的并发工具类_第4张图片

同步用法:ForkJoinPool 的 invoke() 方法是同步提交的,就是调用了 invoke() 方法后主线程后面的代码要等待统计完才会执行。
异步用法:ForkJoinPool 的 execute() 方法是异步提交的,就是调用了 execute() 方法后主线程面的代码会继续执行。没有返回值。
异步用法:ForkJoinPool 的 submit() 方法是异步提交的,就是调用了 submit() 方法后主线程面的代码会继续执行。有返回值。
有返回值:RecursiveTask< V >
没有返回值:RecursiveAction
compute() 方法,

1.3 Fork-Join实战:

java 中 Arrays 中的 parallelSort() 方法中使用到了 Fork-Join
1,Fork-Join 的同步用法,同时演示返回结果值:统计整形数组中所有元素的和

代码示例:
公共类

package com.h.concurrent.ch2.forkjoin.sum;

import java.util.Random;

/**
 * @author h
 * @Description 公共类
 * @createTime 2020年03月16日 22:08
 */
public class MakeArray {

    // 数组长度
    public static final int ARRAY_LENGTH = 4000;

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

使用单线程,直接计算:

package com.h.concurrent.ch2.forkjoin.sum;

import java.util.concurrent.TimeUnit;

/**
 * @author h
 * @Description 用单线程做求和
 * 不加休眠时间时:The count is 23759914 spend time: 0
 * 添加休眠时间后:The count is 23712059 spend time: 4225
 * @createTime 2020年03月16日 22:12
 */
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++) {
            try { TimeUnit.MILLISECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}
            count = count + src[i];
        }
        System.out.println("The count is " + count
                + " spend time: " + (System.currentTimeMillis() - start));
    }
}

使用 Fork-Join(继承 RecursiveTask 的方式):

package com.h.concurrent.ch2.forkjoin.sum;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

/**
 * @author h
 * @Description 使用fork-join进行求和     RecursiveTask 有返回值的
 * 步骤:
 * 1,继承 RecursiveTask/RecursiveAction
 * 2,重写 compute() 方法,
 *      满足条件时
 *          3,进行逻辑处理,
 *      不满足条件时继续拆分任务。
 *         4,调用父类的 invokeAll 方法,把需要继续拆分的任务交给 ForkJoinTask 继续拆分处理
 *         5,返回处理的结果 return left.join() + right.join(); 如果是继承 RecursiveAction 类则不需要返回
 * 6,创建出池的实例 ForkJoinPool,ForkJoinPool pool = new ForkJoinPool();
 * 7,调用 pool.invoke(sumTask); 方法,该方法是同步方法
 * 8,调用 sumTask.join() 获取返回结果,如果是不需要获取返回值时,可以不调用。
 * @createTime 2020年03月16日 22:17
 */
public class SumArrayByForkJoin {

    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) {
                System.out.println(" from Index = "+ fromIndex + " to Index = " + toIndex);
                int count = 0;
                for (int i = fromIndex; i <= toIndex; i++) {
                    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);
                // 调用父类的 invokeAll 方法,把需要继续拆分的任务交给 ForkJoinTask 继续拆分处理
                invokeAll(left, right);
                return left.join() + right.join();
            }
        }
    }

    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        int[] src = MakeArray.makeArray();

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

        long start = System.currentTimeMillis();

        pool.invoke(sumTask);
        System.out.println(" Task is Running....");

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

2,Fork-Join 使用的方式二:
计算 0-100 的和,步骤如下:

  • 1,创建任务池,实际上就是线程池
  • 2,创建任务类
  • 3,通过线程池,执行任务,编写任务拆分规则
  • 4,获取返回结果
  • 5,关闭线程池
package com.h.concurrent.ch2.forkjoin.sum;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

/**
 * @author h
 * @Description ForkJoin 的使用方式二
 * @createTime 2020年03月17日 22:08
 */
public class SumByForkJoin extends RecursiveTask<Integer> {

    private static final int THRESHOLD = 10;
    private int begin;
    private int end;
    private int result;

    public SumByForkJoin(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }
    public int getBegin() {
        return begin;
    }
    public void setBegin(int begin) {
        this.begin = begin;
    }
    public int getEnd() {
        return end;
    }
    public void setEnd(int end) {
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if ((end - begin) <= THRESHOLD) {
            for (int i = begin; i <= end; i++) {
                result = result + i;
            }
        } else {
            int mid = (begin + end)/2;
            SumByForkJoin myTask01 = new SumByForkJoin(begin, mid);
            SumByForkJoin myTask02 = new SumByForkJoin(mid + 1, end);
            myTask01.fork();
            myTask02.fork();
            result = myTask01.join() + myTask02.join();
        }
        return result;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1,创建任务池,实际上就是线程池
        ForkJoinPool threadPool = new ForkJoinPool();
        // 2,创建任务类
        SumByForkJoin myTask = new SumByForkJoin(0, 100);
        // 3,通过线程池,执行任务,编写任务拆分规则
        ForkJoinTask<Integer> submit = threadPool.submit(myTask);
        // 4,获取返回结果
        Integer result = submit.get();
        System.out.println(result);
        // 5,关闭线程池
        threadPool.shutdown();
    }
}

3,Fork-Join 的异步用法,同时演示不要求返回值:便利指定目录(含子目录)寻找指定类型文件

代码示例如下:

package com.h.concurrent.ch2.forkjoin;

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

/**
 * 类说明:遍历指定目录(含子目录)找寻指定类型文件
 */
public class FindDirsFiles extends RecursiveAction {

    private File path;

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


    @Override
    protected void compute() {
        //
        List<FindDirsFiles> subTask = new ArrayList<>();
        //
        File[] files = path.listFiles();
        if (files != null) {
            for (File file : files) {
                if (!file.isDirectory()) {
                    if (file.getAbsolutePath().endsWith(".txt")) {
                        System.out.println(file.getAbsolutePath());
                    }
                } else {
                    FindDirsFiles findDirsFiles = new FindDirsFiles(file);
                    subTask.add(findDirsFiles);
                    findDirsFiles.fork(); // 方式一:直接使用任务类的 fork() 方法
//                    findDirsFiles.join();
                }
            }
            // 方式二:通过调用 ForkJoinPool线程池的 invokeAll(subTask) 方法来调用所有任务;
            // 在当前的 ForkJoinPool 上调度所有的任务
//            if(!subTask.isEmpty()){
//                Collection findDirsFiles = invokeAll(subTask);
//                for (FindDirsFiles findDirsFile : findDirsFiles) {
//                    findDirsFile.join();
//                }
//            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        // 1,创建任务池
        ForkJoinPool pool = new ForkJoinPool();

        String path = "F:\\";
        // 2,创建任务(可以创建带返回值的和不带返回值的),并编写处理逻辑
        FindDirsFiles findDirsFiles = new FindDirsFiles(new File(path));

        // 3,通过任务池执行任务,可以同步执行(invoke)和异步执行(execute)
        pool.execute(findDirsFiles);

        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);

        // 4,获取返回结果
        findDirsFiles.join();
        System.out.println("Task end....");
    }
}

2,CountDownLatch 的作用、应用场景和实战

2.1 CountDownLatch 的作用

CountDownLatch 也叫 “闭锁”、“发令枪”。应用场景是,当一个或多个工作线程,要等待预备线程或者初始话线程,把相关初始化工作做完了,才让工作线程继续往下执行。

2.1 CountDownLatch 的应用场景

2.3 实战

TW1/TW2 是主线程 调用了 await() 方法并且设置了计数为 5。
注意:

  • 计数器的扣减数和线程数可以不一样,两者数量可以相同也可以不相同。
  • 一个线程可以调用多次 countDown() 方法。
  • 工作线程调用了 countDown() 方法后,如果代码没有执行完会继续执行,不受计数是否结束的影响
  • 等待线程数可以有多个,await() 方法可以使用多次,既可以使用在主线程中,也可以同时使用在子线程中。

*java并发编程:2,线程的并发工具类_第5张图片
上面流程图的代码示例:

package com.h.concurrent.ch2.tools;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * @author h
 * @Description 演示 CountDownLatch 的使用
 * @createTime 2020年03月17日 23:11
 */
public class UseCountDownLatch {

    private static CountDownLatch countDownLatch = new CountDownLatch(5);

    private static class InitThread extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+" 的 run() 方法开始执行。。。。");
            countDownLatch.countDown();
            System.out.println(Thread.currentThread().getName()+" 线程调用完 countDown()方法后继续执行。。。。");
        }
    }

    private static class Td extends Thread{
        @Override
        public void run() {
            try { TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}
            System.out.println(Thread.currentThread().getName()
                    + " ready init work step 1st.......");
            countDownLatch.countDown();
            System.out.println(Thread.currentThread().getName()
                    + " ready init work step 2st.......");
            // countDown() 可以在同一个线程中调用多次
            countDownLatch.countDown();
            try { TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}
            System.out.println(Thread.currentThread().getName()
                    + " 计数结束后,Td 继续执行.......");
        }
    }

    private static class TW2 extends Thread{
        @Override
        public void run() {
            System.out.println("TW2 的 run() 方法开始执行。。。。");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("TW2 的 run() 方法执行结束。。。。");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        InitThread ta = new InitThread();
        ta.setName("Ta");
        ta.start();

        InitThread tb = new InitThread();
        tb.setName("Tb");
        tb.start();

        InitThread tc = new InitThread();
        tc.setName("Tc");
        tc.start();

        Td td = new Td();
        td.setName("Td");
        td.start();

        TW2 tw2 = new TW2();
        tw2.start();

        countDownLatch.await();
        System.out.println("TW1 do ites work.......");
    }
}

控制台打印结果如下:

Ta 的 run() 方法开始执行。。。。
Ta 线程调用完 countDown()方法后继续执行。。。。
TW2 的 run() 方法开始执行。。。。
Tc 的 run() 方法开始执行。。。。
Tc 线程调用完 countDown()方法后继续执行。。。。
Tb 的 run() 方法开始执行。。。。
Tb 线程调用完 countDown()方法后继续执行。。。。
Td ready init work step 1st.......
Td ready init work step 2st.......
TW1 do ites work.......
TW2 的 run() 方法执行结束。。。。
Td 计数结束后,Td 继续执行.......

3,CyclicBarrier 的作用、应用场景和实战

3.1 CyclicBarrier 的作用及应用场景

要等到所有的线程都执行到屏障时,所有线程同时继续往后执行。即 Ta 和 Tb 先达到屏障要等到 Tc 也达到屏障时,三个线程才同时往下执行。
可以通过 BarrierAction 把子线程执行完的结果进行汇总。
*java并发编程:2,线程的并发工具类_第6张图片
注意事项:

  • 1,线程是和计数点要保持一致
  • 2,只要启动工作线程即可,当有与计数相同数量的工作线程调用了await() 方法,则 BarrierAction 线程会自动启动并执行
  • 3,如果在工作线程多次调用了 cyclicBarrier 的await()方法,则统计线程会执行多次统计。

3.2 CyclicBarrier 的应用场景

3.3 CyclicBarrier 实战

package com.h.concurrent.ch2.tools;

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

/**
 * @author h
 * @Description 演示 CyclicBarrier用法,共3个线程,它们全部完成工作后,
 *交出自己的结果,再被统一释放去做自己的事情,而交出的结果被另外的线程拿来拼接字符串
 * 注意:
 *      只要启动工作线程即可,当有与计数相同数量的工作线程调用了await() 方法,则 BarrierAction 线程会自动启动并执行
 *      如果在工作线程多次调用了 cyclicBarrier 的await()方法,则统计线程会执行多次统计。
 * @createTime 2020年03月19日 21:40
 */
public class UseCyclicBarrier {

    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new BarrierAction());

    private static ConcurrentHashMap<String, String> workMap = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        Thread ta = new Thread(new SubThread());
        ta.setName("Ta");
        ta.start();
        Thread tb = new Thread(new SubThread());
        tb.setName("Tb");
        tb.start();
        Thread tc = new Thread(new SubThread());
        tc.setName("Tc");
        tc.start();
    }

    private static class BarrierAction extends Thread{
        @Override
        public void run() {
            System.out.println("BarrierAction 开始执行。。。");
            StringBuilder buffer = new StringBuilder("[ ");
            for (Map.Entry<String, String> entry : workMap.entrySet()) {
                buffer.append(entry.getValue());
                buffer.append(" ");
            }
            buffer.append(" ]");
            System.out.println(buffer.toString());
            System.out.println("BarrierAction 执行结束。。。");
        }
    }

    private static class SubThread implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " do sth....");
            workMap.put(Thread.currentThread().getName(), Thread.currentThread().getName());
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " do sth.... end");

            try {
                cyclicBarrier.await();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

控制台打印结果如下:

Ta do sth....
Tc do sth....
Tb do sth....
BarrierAction 开始执行。。。
[ Ta Tb Tc  ]
BarrierAction 执行结束。。。
Tb do sth.... end
Ta do sth.... end
Tc do sth.... end
BarrierAction 开始执行。。。
[ Ta Tb Tc  ]
BarrierAction 执行结束。。。

4,CountDownLatch 和 CyclicBarrier 的区别

  • 1,从构造函数上看,CountDownLatch 的扣减数和线程数可以不同,CyclicBarrier 的线程数和计数一定要相同
  • 2,CountDownLatch 的计数方法 countDown() 调用次数和 扣减数保持一致, CyclicBarrier 的计数方法 await() 方法可以反复调用
  • 3,在协调线程之间同时运行方面,CountDownLatch 工作线程的协调执行是通过预备线程协调执行的,CyclicBarrier 是工作线程本身相互协调执行的
  • CountDownLatch 在工作线程执行的时候,不能统计结果;CyclicBarrier 可以通过 BarrierAction 进行汇总统计子线程计算的结果

5,Semaphore 的作用、应用场景和实战

5.1 信号量的作用

  • 1,用于多个共享资源的互斥使用
  • 2,用于并发线程数的控制。
    主要用于做流量控制,类似于抢车位,有四个车位,有很多辆车在等待,但是车位只有四个,没有等到车位的人,只有等到有车位空缺时才能进入停车位。
    *java并发编程:2,线程的并发工具类_第7张图片
    *java并发编程:2,线程的并发工具类_第8张图片
    acquire() 方法: 查看当前是否有空闲的位置,如果没有则会让线程进入等待状态
    release() 方法: 释放占有的位置

如上图所示:

  • Semaphore 中设置了 4 个许可证(a);这个时候有一个线程调用了 acquire() 方法(b);Semaphore 会从4个许可证中拿出一个(c);只有这个线程调用了release() 方法才会把许可证归还给 Semaphore
  • 当有大量的线程调用了 acquire() 方法(e);会有4个线程可以同时获取到许可证(f),剩余的线程就会进入等待状态;直到有线程调用了release() 方法把许可证归还给 Semaphore 后,处于等待中的线程就会竞争这个许可证,没有抢到的继续等待,抢到的继续执行业务代码。
  • 虽然 Semaphore 对流量进行了限制,但是并不代表每次只有一个线程获取到许可证。上图中初始化了4个许可证,表示同时可以有4个线程获取到许可证,所以连接池还是需要使用synchronized关键字或其他方式保证线程安全。

5.2 Semaphore 应用场景

用 Semaphore 实现数据库连接池

Semaphore 注意事项:
Semaphore 如果不调用acquire()方法,只调用了release()方法,那么许可证的个数会不断的上升

5.3 Semaphore 实战

公用代码

/**
 * @author h
 * @Description 数据库连接的平庸实现
 * @createTime 2020年03月19日 22:10
 */
public class SqlConnectionImpl implements Connection {
    /*获取一个数据库连接*/
    public static final SqlConnectionImpl fetchConnection(){
        return new SqlConnectionImpl();
    }
    @Override
    public Statement createStatement() throws SQLException {
        return null;
    }
    // 剩余实现方法省略。
}  

许可证的个数会不断的上升,没有达到流量控制作用的场景,代码示例

package com.h.concurrent.ch2.tools.semaphore;

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

/**
 * @author h
 * @Description 演示 Semaphore用法,一个数据库连接池的实现
 * Semaphore 如果不调用acquire()方法,只调用了release()方法,那么许可证的个数会不断的上升
 * @createTime 2020年03月19日 22:12
 */
public class DBPoolNoUseLess {

    public final Semaphore useful;
    private static final int POOL_SIZE = 10;

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

    /*初始化线程池*/
    static {
        for (int i = 0; i < POOL_SIZE; i++) {
            pool.addLast(SqlConnectionImpl.fetchConnection());
        }
    }
    public DBPoolNoUseLess() {
        this.useful = new Semaphore(10);
    }
    /**
     * 获取连接
     */
    public SqlConnectionImpl takeConnection() throws InterruptedException {
        useful.acquire();
        synchronized (pool) {
            if (!pool.isEmpty()) {
                System.out.println("成功获取连接。");
                return pool.removeFirst();
            }
        }
        return null;
    }

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

    private static DBPoolNoUseLess dbPoolNoUseLess = new DBPoolNoUseLess();
    private static class WorkThread extends Thread {
        @Override
        public void run() {
            dbPoolNoUseLess.returnConnection(new SqlConnectionImpl());

           /* SqlConnectionImpl sqlConnection = null;
            try {
                sqlConnection = dbPoolNoUseLess.takeConnection();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                if(null != sqlConnection) {
                    dbPoolNoUseLess.returnConnection(sqlConnection);
                }
            }*/
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            new WorkThread().start();
        }
    }
}

确保可以达到流量控制的代码示例

通过定义两个Semaphore ,防止了只调用 release() 方法的场景,保证了流量控制。

package com.h.concurrent.ch2.tools.semaphore;

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

/**
 * @author h
 * @Description TODO
 * @createTime 2020年03月19日 22:58
 */
public class DBPoolSemaphore {
    // 两个指示器,分别表示池子的可用连接和已用连接
    private final Semaphore useful;
    private final Semaphore useless;
    private static final int POOL_SIZE = 10;
    private static LinkedList<Connection> pool = new LinkedList<Connection>();

    static {
        for (int i = 0; i < POOL_SIZE; i++) {
            pool.addLast(SqlConnectionImpl.fetchConnection());
        }
    }

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

    /**
     * 获取连接
     */
    public Connection takeConnection() throws InterruptedException {
        useful.acquire();
        Connection connection = null;
        synchronized (pool) {
            if (!pool.isEmpty()) {
                connection = pool.removeFirst();
            }
        }
        useless.release();
        return connection;
    }

    /**
     * 归还连接
     */
    public void returnConnection(Connection connection) throws InterruptedException {
        if (null != connection) {
            System.out.println("当前有"+ useful.getQueueLength()+"个线程等待数据库连接!!"+"可用连接数:"+useful.availablePermits() + " " + System.currentTimeMillis());
            // 这里先调用可用连接的 acquire(),由于 useless 定义的初始许可证个数为 0,所以如果一上来就先调用 useless.acquire() 则会处于等待状态
            useless.acquire();
            System.out.println("==== = =========== ========== ==== ========== === == = =");
            synchronized (pool) {
                pool.addLast(connection);
            }
            useful.release();
        }
    }
}

测试类:

package com.h.concurrent.ch2.tools.semaphore;

import java.sql.Connection;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author h
 * @Description Semaphore 测试类
 * @createTime 2020年03月19日 23:13
 */
public class AppTest {

    private static DBPoolSemaphore pool = new DBPoolSemaphore();

    private static class WorkThread extends Thread{
        @Override
        public void run() {
            try {
                Connection connection = pool.takeConnection();
                System.out.println(" --- -- --- -- -- -- -- - -- -- " + System.currentTimeMillis());
                Random random = new Random();
                try { TimeUnit.MILLISECONDS.sleep(random.nextInt(1000));} catch (Exception e) {e.printStackTrace();}
                pool.returnConnection(connection);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 500; i++) {
            WorkThread workThread = new WorkThread();
            workThread.start();
        }
    }
}

6,Exchange 的作用、应用场景和实战

6.1 Exchange 的作用

只支持两个线程之间执行完之后,交换执行结果
*java并发编程:2,线程的并发工具类_第9张图片

6.2 Exchange 的应用场景

6.1 Exchange 实战

package com.h.concurrent.ch2.tools;

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

/**
 * @author h
 * @Description 演示 Exchanger 用法
 * @createTime 2020年03月19日 23:37
 */
public class UseExchange {

    private static final Exchanger<Set<String>> exchange = new Exchanger<>();

    public static void main(String[] args) {

        new Thread(() ->{
            // 初始化数据
            Set<String> setA = new HashSet<>();
            setA.add("a1");
            setA.add("a2");
            setA.add("a3");
            // 交换数据,把 setA交换给BB线程,从BB线程中获取setB
            try {
                setA = exchange.exchange(setA);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 打印交换后的结果
            setA.forEach(s -> System.out.println(Thread.currentThread().getName() +" "+ s));
        }, "AA").start();

        new Thread(() ->{
            // 初始化数据
            Set<String> setB = new HashSet<>();
            setB.add("b1");
            setB.add("b2");
            setB.add("b3");
            // 交换数据,把 setB交换给AA线程,从AA线程中获取setA
            try {
                setB = exchange.exchange(setB);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 打印交换后的结果
            setB.forEach(s -> System.out.println(Thread.currentThread().getName() +" "+ s));
        }, "BB").start();
    }
}

控制台打印结果如下:

AA b2
AA b3
AA b1
BB a1
BB a2
BB a3

7,Callable、Future 和 FutureTask

JDK开始设计线程的时候只有 Thread 和 Runnable,而它们的 run() 方法都是没有返回值的。在实际业务场景中可能会有需要带返回值的线程,所以设计了 Callable 接口,但是原有的 Thread 类并没有传入 Callable 接口的构造函数,为了解决这个问题,借助了一个中间类 FutureTask。

FutureTask

  • 实现了 RunnableFuture 接口,RunnableFuture 接口又继承了 Runnable,并且FutureTask 类的构造函数支持 Callable作为参数传参。
  • cancel() 方法,用于中断线程,底层是调用了 Thread 类的 interrupt(); 方法,所以如果需要真正的中断线程,要在线程的run() 方法中使用 isinterrupted(); 方法判断然后自行处理退出线程。
public boolean cancel(boolean mayInterruptIfRunning) {
    if (!(state == NEW &&
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    // in case call to interrupt throws exception
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();
            } finally { // final state
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        finishCompletion();
    }
    return true;
}

Future 接口

  • 有一个 get() 方法,可以获取线程的返回值。除此之外,Future 还可以终止线程。

代码示例如下:

FutureTask task = new FutureTask(new Callable());
Thread t = new Thread(task );
t.start();
task.get();

*java并发编程:2,线程的并发工具类_第10张图片

7.2 Callable、Future 和 FutureTask 实战

代码示例如下:

package com.h.concurrent.ch2.future;

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

/**
 * @author h
 * @Description 演示Callable的使用
 * @createTime 2020年03月19日 23:57
 */
public class UseFuture {

    private static class UseCallable implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            System.out.println("开始计算。。。");
            int sum = 0;
            for (int i = 0; i < 10000; i++) {
                sum = sum + i;
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("线程中断。。。。退出");
                    return null;
                }
                System.out.println("正在计算。。。。。sum = " + sum);
            }
            System.out.println("计算结束...");
            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();
        System.out.println("调用 get() 前。");
//        System.out.println("计算结果为:" + futureTask.get());
        System.out.println("调用 get() 后。");
        try { TimeUnit.MILLISECONDS.sleep(5);} catch (Exception e) {e.printStackTrace();}

        futureTask.cancel(true);
    }
}

你可能感兴趣的:(并发编程)