JUC 常用 4 大并发工具类是哪几个?(面试必问)(1)

简介: JUC 常用 4 大并发工具类是哪几个?(面试必问)

什么是JUC?

JUC就是java.util.concurrent包,这个包俗称JUC,里面都是解决并发问题的一些东西

该包的位置位于java下面的rt.jar包下面


4大常用并发工具类:

CountDownLatch

CyclicBarrier

Semaphore

ExChanger


CountDownLatch:

CountDownLatch,俗称闭锁,作用是类似加强版的Join,是让一组线程等待其他的线程完成工作以后才执行

就比如在启动框架服务的时候,我们主线程需要在环境线程初始化完成之后才能启动,这时候我们就可以实现使用CountDownLatch来完成


/**
 * Constructs a {@code CountDownLatch} initialized with the given count.
 *
 * @param count the number of times {@link #countDown} must be invoked
 *        before threads can pass through {@link #await}
 * @throws IllegalArgumentException if {@code count} is negative
 */
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}


在源码中可以看到,创建CountDownLatch时,需要传入一个int类型的参数,将决定在执行次扣减之后,等待的线程被唤醒

image.png



通过这个类图就可以知道其实CountDownLatch并没有多少东西


方法介绍:


CountDownLatch:初始化方法


await:等待方法,同时带参数的是超时重载方法


countDown:每执行一次,计数器减一,就是初始化传入的数字,也代表着一个线程完成了任务


getCount:获取当前值


toString:这个就不用说了


里面的Sync是一个内部类,外面的方法其实都是操作这个内部类的,这个内部类继承了AQS,实现的标准方法,AQS将在后面的章节写


image.png



主线程中创建CountDownLatch(3),然后主线程await阻塞,然后线程A,B,C各自完成了任务,调用了countDown,之后,每个线程调用一次计数器就会减一,初始是3,然后A线程调用后变成2,B线程调用后变成1,C线程调用后,变成0,这时就会唤醒正在await的主线程,然后主线程继续执行


说一千道一万,不如代码写几行,上代码:


休眠工具类,之后的代码都会用到


package org.dance.tools;
import java.util.concurrent.TimeUnit;
/**
 * 类说明:线程休眠辅助工具类
 */
public class SleepTools {
    /**
     * 按秒休眠
     * @param seconds 秒数
     */
    public static final void second(int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
        }
    }
    /**
     * 按毫秒数休眠
     * @param seconds 毫秒数
     */
    public static final void ms(int seconds) {
        try {
            TimeUnit.MILLISECONDS.sleep(seconds);
        } catch (InterruptedException e) {
        }
    }
}
package org.dance.day2.util;
import org.dance.tools.SleepTools;
import java.util.concurrent.CountDownLatch;
/**
 * CountDownLatch的使用,有五个线程,6个扣除点
 * 扣除完成后主线程和业务线程,才能执行工作
 *  扣除点一般都是大于等于需要初始化的线程的
 * @author ZYGisComputer
 */
public class UseCountDownLatch {
    /**
     * 设置为6个扣除点
     */
    static CountDownLatch countDownLatch = new CountDownLatch(6);
    /**
     * 初始化线程
     */
    private static class InitThread implements Runnable {
        @Override
        public void run() {
            System.out.println("thread_" + Thread.currentThread().getId() + " ready init work .....");
            // 执行扣减 扣减不代表结束
            countDownLatch.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 {
                countDownLatch.await();
                for (int i = 0; i < 3; i++) {
                    System.out.println("BusiThread " + Thread.currentThread().getId() + " do business-----");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        // 创建单独的初始化线程
        new Thread(){
            @Override
            public void run() {
                SleepTools.ms(1);
                System.out.println("thread_" + Thread.currentThread().getId() + " ready init work step 1st.....");
                // 扣减一次
                countDownLatch.countDown();
                System.out.println("begin stop 2nd.....");
                SleepTools.ms(1);
                System.out.println("thread_" + Thread.currentThread().getId() + " ready init work step 2nd.....");
                // 扣减一次
                countDownLatch.countDown();
            }
        }.start();
        // 启动业务线程
        new Thread(new BusiThread()).start();
        // 启动初始化线程
        for (int i = 0; i <= 3; i++) {
            new Thread(new InitThread()).start();
        }
        // 主线程进入等待
        try {
            countDownLatch.await();
            System.out.println("Main do ites work.....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

返回结果:

thread_13 ready init work .....
thread_13.....continue do its work
thread_13.....continue do its work
thread_14 ready init work .....
thread_14.....continue do its work
thread_14.....continue do its work
thread_15 ready init work .....
thread_15.....continue do its work
thread_11 ready init work step 1st.....
begin stop 2nd.....
thread_16 ready init work .....
thread_16.....continue do its work
thread_16.....continue do its work
thread_15.....continue do its work
thread_11 ready init work step 2nd.....
Main do ites work.....
BusiThread 12 do business-----
BusiThread 12 do business-----
BusiThread 12 do business-----


通过返回结果就可以很直接的看到业务线程是在初始化线程完全跑完之后,才开始执行的


CyclicBarrier:

CyclicBarrier,俗称栅栏锁,作用是让一组线程到达某个屏障,被阻塞,一直到组内的最后一个线程到达,然后屏障开放,接着,所有的线程继续运行


这个感觉和CountDownLatch有点相似,但是其实是不一样的,所谓的差别,将在下面详解

CyclicBarrier的构造参数有两个


/**
 * Creates a new {@code CyclicBarrier} that will trip when the
 * given number of parties (threads) are waiting upon it, and
 * does not perform a predefined action when the barrier is tripped.
 *
 * @param parties the number of threads that must invoke {@link #await}
 *        before the barrier is tripped
 * @throws IllegalArgumentException if {@code parties} is less than 1
 */
public CyclicBarrier(int parties) {
    this(parties, null);
}
/**
 * Creates a new {@code CyclicBarrier} that will trip when the
 * given number of parties (threads) are waiting upon it, and which
 * will execute the given barrier action when the barrier is tripped,
 * performed by the last thread entering the barrier.
 *
 * @param parties the number of threads that must invoke {@link #await}
 *        before the barrier is tripped
 * @param barrierAction the command to execute when the barrier is
 *        tripped, or {@code null} if there is no action
 * @throws IllegalArgumentException if {@code parties} is less than 1
 */
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    this.barrierCommand = barrierAction;
}


很明显能感觉出来,上面的构造参数调用了下面的构造参数,是一个构造方法重载


首先这个第一个参数也树Int类型的,传入的是执行线程的个数,这个数量和CountDownLatch不一样,这个数量是需要和线程数量吻合的,CountDownLatch则不一样,CountDownLatch可以大于等于,而CyclicBarrier只能等于,然后是第二个参数,第二个参数是barrierAction,这个参数是当屏障开放后,执行的任务线程,如果当屏障开放后需要执行什么任务,可以写在这个线程中


image.png


主线程创建CyclicBarrier(3,barrierAction),然后由线程开始执行,线程A,B执行完成后都调用了await,然后他们都在一个屏障前阻塞者,需要等待线程C也,执行完成,调用await之后,然后三个线程都达到屏障后,屏障开放,然后线程继续执行,并且barrierAction在屏障开放的一瞬间也开始执行


上代码:

package org.dance.day2.util;
import org.dance.tools.SleepTools;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
/**
 * CyclicBarrier的使用
 *
 * @author ZYGisComputer
 */
public class UseCyclicBarrier {
    /**
     * 存放子线程工作结果的安全容器
     */
    private static ConcurrentHashMap<String, Long> resultMap = new ConcurrentHashMap<>();
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5,new CollectThread());
    /**
     * 结果打印线程
     * 用来演示CyclicBarrier的第二个参数,barrierAction
     */
    private static class CollectThread implements Runnable {
        @Override
        public void run() {
            StringBuffer result = new StringBuffer();
            for (Map.Entry<String, Long> workResult : resultMap.entrySet()) {
                result.append("[" + workResult.getValue() + "]");
            }
            System.out.println("the result = " + result);
            System.out.println("do other business.....");
        }
    }
    /**
     * 工作子线程
     * 用于CyclicBarrier的一组线程
     */
    private static class SubThread implements Runnable {
        @Override
        public void run() {
            // 获取当前线程的ID
            long id = Thread.currentThread().getId();
            // 放入统计容器中
            resultMap.put(String.valueOf(id), id);
            Random random = new Random();
            try {
                if (random.nextBoolean()) {
                    Thread.sleep(1000 + id);
                    System.out.println("Thread_"+id+"..... do something");
                }
                System.out.println(id+" is await");
                cyclicBarrier.await();
                Thread.sleep(1000+id);
                System.out.println("Thread_"+id+".....do its business");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i <= 4; i++) {
            Thread thread = new Thread(new SubThread());
            thread.start();
        }
    }
}

返回结果:

11 is await
14 is await
15 is await
Thread_12..... do something
12 is await
Thread_13..... do something
13 is await
the result = [11][12][13][14][15]
do other business.....
Thread_11.....do its business
Thread_12.....do its business
Thread_13.....do its business
Thread_14.....do its business
Thread_15.....do its business

通过返回结果可以看出前面的11 14 15三个线程没有进入if语句块,在执行到await的时候进入了等待,而另外12 13两个线程进入到了if语句块当中,多休眠了1秒多,然后当5个线程同时到达await的时候,屏障开放,执行了barrierAction线程,然后线程组继续执行


解释一下CountDownLatch和CyclicBarrier的却别吧!


首先就是CountDownLatch的构造参数传入的数量一般都是大于等于线程,数量的,因为他是有第三方控制的,可以扣减多次,然后就是CyclicBarrier的构造参数第一个参数传入的数量一定是等于线程的个数的,因为他是由一组线程自身控制的


区别

CountDownLatch  CyclicBarrier

控制   第三方控制     自身控制

传入数量  大于等于线程数量 等于线程数量



相关文章
|
3月前
|
存储 缓存 并行计算
【面试问题】JDK并发类库提供的线程池实现有哪些?
【1月更文挑战第27天】【面试问题】JDK并发类库提供的线程池实现有哪些?
|
3月前
|
存储 前端开发 JavaScript
【面试题】面试官问:如果有100个请求,你如何使用Promise控制并发?
【面试题】面试官问:如果有100个请求,你如何使用Promise控制并发?
|
3月前
|
存储 前端开发 JavaScript
面试官问:如果有100个请求,你如何使用Promise控制并发?
面试官问:如果有100个请求,你如何使用Promise控制并发?
|
2月前
|
存储 前端开发 JavaScript
前端面试:如何实现并发请求数量控制?
前端面试:如何实现并发请求数量控制?
84 0
|
3月前
|
存储 缓存 NoSQL
《吊打面试官》系列-Redis双写一致性、并发竞争、线程模型
《吊打面试官》系列-Redis双写一致性、并发竞争、线程模型
40 0
|
3月前
|
安全 Linux Go
golang面试:golang并发与多线程(三)
golang面试:golang并发与多线程(三)
45 0
|
4月前
|
Java 数据库 索引
最强阿里及大厂350道面试大全:框架+数据库+并发+开源+微服务
无论是对于刚入行工作还是已经工作几年的java开发者来说,面试求职始终是你需要直面的一件事情。首先梳理自己的知识体系,针对性准备,会有事半功倍的效果。我们往往会把重点放在技术上,而忽略了人事部分,实际上人事面试也会影响到最终的结果,把每一个环节做好,最终的结果自然不会差。
|
4月前
|
消息中间件 算法 NoSQL
45k以上突击面试必备,redis+mysql+并发+spring+算法+导图等
今天小编给大家带来的一篇关于Java面试相关的电子文档资源,介绍了关于Java、面试题方面的内容,本书是由Java官网出版,格式为DOC,资源大小62.5 MB,目前豆瓣、亚马逊、当当、京东等电子书综合评分为:8.7。
|
5月前
|
存储 安全 Java
Java并发篇:6个必备的Java并发面试种子题目
文章涉及了几个常见的并发编程相关的主题。首先,线程的创建和生命周期是面试中常被问及的话题,面试官可能会询问如何创建线程、线程的状态转换以及如何控制线程的执行顺序等。其次,synchronized关键字是用于实现线程同步的重要工具,面试中可能会涉及到它的使用场景以及与其他同步机制的比较。此外,抽象队列同步器(AQS)是Java并发编程中的核心概念,了解其原理和应用场景可以展示对并发编程的深入理解。最后,面试中可能会考察对Java线程池和Fork/Join框架的了解,包括它们的使用方法、优势和适用场景等。种子题目务必学会
|
5月前
|
Java
面试java并发~(lock、volatile、cas)
面试java并发~(lock、volatile、cas)
42 0