多线程与高并发04-线程的并发工具类

线程的并发工具类

CountDownLatch

原理介绍

设置一个初始值,然后await,减到0,自动往下走
门闩,CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行

CountDownLatch是通过一个计数器来实现的,计数器的初始值为初始任务的数量。每当完成了一个任务后,计数器的值就会减1(CountDownLatch.countDown()方法)。当计数器值到达0时,它表示所有的已经完成了任务,然后在门闩上等待CountDownLath.await()方法的线程就可以恢复执行任务

应用场景

实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用一次countDown()方法就可以让所有的等待线程同时恢复执行。

开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了,例如处理excel中多个表单
image.png
注意:

一个线程可以做多次 countDown(),做了 countDown()的线程不会停止还会继续往下执行

示例代码:

import java.util.concurrent.CountDownLatch;

/**
 *类说明:演示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 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........");
    }
}

运行结果:

Thread_10 ready init work step 1st......
begin step 2nd.......
Thread_12 ready init work......
Thread_13 ready init work......
Thread_14 ready init work......
Thread_14 ........continue do its work
Thread_12 ........continue do its work
Thread_14 ........continue do its work
Thread_10 ready init work step 2nd......
Thread_13 ........continue do its work
Thread_15 ready init work......
Thread_12 ........continue do its work
Thread_15 ........continue do its work
Thread_15 ........continue do its work
Thread_13 ........continue do its work
BusiThread_11 do business-----
BusiThread_11 do business-----
BusiThread_11 do business-----
Main do ites work........

CyclicBarrier

原理介绍

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞

CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrierAction),用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景

应用场景

CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景

用处:
复杂操作
1. 数据库
2. 网络
3. 文件
并发执行
- 线程 - 操作
- 线程 - 操作
- 线程 - 操作
等待三个线程操作完毕流程才继续往下执行(满员执行概念)

image.png

示例代码:

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

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

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

    //存放子线程工作结果的容器
    private static ConcurrentHashMap 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 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();
            }

        }
    }
}

运行结果:

Thread_10 ....do something 
Thread_11 ....do something 
Thread_13 ....do something 
Thread_12 ....do something 
 the result = [11][12][13][10]
do other business........
Thread_10 ....do its business 
Thread_13 ....do its business 
Thread_12 ....do its business 
Thread_11 ....do its business 
 the result = [11][12][13][10]
do other business........

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的用法也很简单,首先线程使用Semaphoreacquire()方法获取一个许可证,使用完之后调用release()方法归还许可证。还可以用tryAcquire()方法尝试获取许可证。

Semaphore还提供一些其他方法,具体如下:

  • intavailablePermits():返回此信号量中当前可用的许可证数
  • intgetQueueLength():返回正在等待获取许可证的线程数
  • booleanhasQueuedThreads():是否有线程正在等待获取许可证
  • void reducePermits(int reduction):减少reduction个许可证,是个protected方法
  • Collection getQueuedThreads():返回所有等待获取许可证的线程集合,是个protected方法

image.png
示例代码:

import java.util.concurrent.Semaphore;

public class T11_TestSemaphore {
    public static void main(String[] args) {
        //Semaphore s = new Semaphore(2);
        Semaphore s = new Semaphore(2, true);
        //允许一个线程同时执行
        //Semaphore s = new Semaphore(1);

        new Thread(()->{
            try {
                s.acquire();

                System.out.println("T1 running...");
                Thread.sleep(200);
                System.out.println("T1 running...");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                s.release();
            }
        }).start();

        new Thread(()->{
            try {
                s.acquire();

                System.out.println("T2 running...");
                Thread.sleep(200);
                System.out.println("T2 running...");

                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

运行结果:

T1 running...
T2 running...
T1 running...
T2 running...

Exchanger

原理介绍

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

应用场景

用于两个线程同步数据交换

示例代码:

import java.util.concurrent.Exchanger;

public class T12_TestExchanger {

    static Exchanger exchanger = new Exchanger<>();

    public static void main(String[] args) {
        new Thread(()->{
            String s = "T1";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);

        }, "t1").start();


        new Thread(()->{
            String s = "T2";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);

        }, "t2").start();


    }
}

运行结果:

t1 T2
t2 T1

Callable、Future和FutureTask

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

image.png

因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask
image.png
因此我们通过一个线程运行Callable,但是Thread不支持构造方法中传递Callable的实例,所以我们需要通过FutureTask把一个Callable包装成Runnable,然后再通过这个FutureTask拿到Callable运行后的返回值。

要new一个FutureTask的实例,有两种方法
image.png
示例代码:

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


/**
 *类说明:演示Future等的使用
 */
public class UseFuture {
    
    
    /*实现Callable接口,允许有返回值*/
    private static class UseCallable implements Callable{
        private int sum;
        @Override
        public Integer call() throws Exception {
            System.out.println("Callable子线程开始计算!");  
//            Thread.sleep(1000);
            for(int i=0 ;i<10;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();

        Thread.sleep(1);
        if(r.nextInt(100)>50){
            System.out.println("Get UseCallable result = "+futureTask.get());
        }else{
            System.out.println("Cancel................. ");
            futureTask.cancel(true);
        }

    }

}

运行结果:

Callable子线程开始计算!
sum=0
sum=1
sum=3
sum=6
sum=10
sum=15
sum=21
sum=28
sum=36
sum=45
Callable子线程计算结束!结果为: 45
Get UseCallable result = 45

你可能感兴趣的:(aqs)