JUC常用并发工具类

JUC常用并发工具类

1、什么是JUC?

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

面的 rt.jar 包下面。

2、4大常用并发工具类

2.1 CountDownLatch

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

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

CountDownLatch 来完成。

public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);
}

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

的线程被唤醒。

CountDownLatch 方法介绍:

  • CountDownLatch:初始化方法。

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

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

  • getCount:获取当前值。

  • toString:这个就不用说了。

CountDownLatch 里面的 Sync 是一个内部类,外面的方法其实都是操作这个内部类的,这个内部类继承了

AQS,实现的标准方法。

CountDownLatch 工作流程:
在这里插入图片描述

主线程中创建 CountDownLatch(3),然后主线程 await 阻塞,然后线程A,B,C各自完成了任务,调用了

countDown 之后,每个线程调用一次计数器就会减一,初始是3,然后A线程调用后变成2,B线程调用后变成1,

C线程调用后变成0,这时就会唤醒正在await的主线程,然后主线程继续执行。

休眠工具类:

package com.utils;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 com.countdownlatch;import com.utils.SleepTools;import java.util.concurrent.CountDownLatch;/*** CountDownLatch的使用,有五个线程,6个扣除点扣除完成后主线程和业务线程才能执行工作* 扣除点一般都是大于等于需要初始化的线程的*/public class UseCountDownLatch {/*** 设置为6个扣除点*/static CountDownLatch countDownLatch = new CountDownLatch(6);/*** 初始化线程*/private static class InitThread implements Runnable {@Overridepublic 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 {@Overridepublic 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() {@Overridepublic 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_14 ready init work .....
thread_17 ready init work .....
thread_16 ready init work .....
thread_15 ready init work .....
thread_16.....continue do its work
thread_17.....continue do its work
thread_14.....continue do its work
thread_17.....continue do its work
thread_16.....continue do its work
thread_15.....continue do its work
thread_14.....continue do its work
thread_15.....continue do its work
thread_12 ready init work step 1st.....
begin stop 2nd.....
thread_12 ready init work step 2nd.....
Main do ites work.....
BusiThread 13 do business-----
BusiThread 13 do business-----
BusiThread 13 do business-----

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

countDownLatch.await()。

2.2 CyclicBarrier

CyclicBarrier,俗称栅栏锁,作用是让一组线程到达某个屏障,被阻塞,一直到组内的最后一个线程到达,然

后屏障开放,接着,所有的线程继续运行。

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

CyclicBarrier 的构造参数有两个:

public CyclicBarrier(int parties) {this(parties, null);
}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,这个参数是当屏障开放后,执行的任务线程,如果当

屏障开放后需要执行什么任务,可以写在这个线程中。

在这里插入图片描述

主线程创建 CyclicBarrier(3,barrierAction),然后由线程开始执行,线程A,B执行完成后都调用了 await,然后

他们都在一个屏障前阻塞者,需要等待线程C也,执行完成,调用 await 之后,然后三个线程都达到屏障后,屏障

开放,然后线程继续执行,并且 barrierAction 在屏障开放的一瞬间也开始执行。

package com.cyclicbarrier;import java.util.Map;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;/*** CyclicBarrier的使用*/
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 {@Overridepublic 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 {@Overridepublic void run() {// 获取当前线程的IDlong 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();}}
}

返回结果:

# 程序输出
13 is await
14 is await
12 is await
Thread_15..... do something
15 is await
Thread_16..... do something
16 is await
the result = [12][13][14][15][16]
do other business.....
Thread_12.....do its business
Thread_13.....do its business
Thread_14.....do its business
Thread_15.....do its business
Thread_16.....do its business

通过返回结果可以看出前面的12 13 14三个线程没有进入if语句块,在执行到await的时候进入了等待,而另外15

16 两个线程进入到了if语句块当中,多休眠了1秒多,然后当5个线程同时到达await的时候,屏障开放,执行了

barrierAction 线程,然后线程组继续执行。

解释一下 CountDownLatch 和 CyclicBarrier 的区别:

首先就是 CountDownLatch 的构造参数传入的数量一般都是大于等于线程数量的,因为他是有第三方控制的,可

以扣减多次,然后就是CyclicBarrier的构造参数第一个参数传入的数量一定是等于线程的个数的,因为他是由一组

线程自身控制的。

区别CountDownLatchCyclicBarrier
控制第三方控制自身控制
传入数量大于等于线程数量等于线程数量

2.3 Semaphore

Semaphore,俗称信号量,作用于控制同时访问某个特定资源的线程数量,用在流量控制。

一说特定资源控制,那么第一时间就想到了数据库连接。

之前用等待超时模式写了一个数据库连接池,打算用这个 Semaphone 也写一个。

public Semaphore(int permits) {sync = new NonfairSync(permits);
}

在源码中可以看到在构建 Semaphore 信号量的时候,需要传入许可证的数量,这个数量就是资源的最大允许的访

问的线程数。接下里用信号量实现一个数据库连接池。

连接对象:

package com.semaphore;import com.utils.SleepTools;import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;/*** 数据库连接*/
public class SqlConnection implements Connection {/*** 获取数据库连接** @return*/public static final Connection fetchConnection() {return new SqlConnection();}@Overridepublic void commit() throws SQLException {SleepTools.ms(70);}@Overridepublic Statement createStatement() throws SQLException {SleepTools.ms(1);return null;}@Overridepublic PreparedStatement prepareStatement(String sql) throws SQLException {return null;}@Overridepublic CallableStatement prepareCall(String sql) throws SQLException {return null;}@Overridepublic String nativeSQL(String sql) throws SQLException {return null;}@Overridepublic void setAutoCommit(boolean autoCommit) throws SQLException {}@Overridepublic boolean getAutoCommit() throws SQLException {return false;}@Overridepublic void rollback() throws SQLException {}@Overridepublic void close() throws SQLException {}@Overridepublic boolean isClosed() throws SQLException {return false;}@Overridepublic DatabaseMetaData getMetaData() throws SQLException {return null;}@Overridepublic void setReadOnly(boolean readOnly) throws SQLException {}@Overridepublic boolean isReadOnly() throws SQLException {return false;}@Overridepublic void setCatalog(String catalog) throws SQLException {}@Overridepublic String getCatalog() throws SQLException {return null;}@Overridepublic void setTransactionIsolation(int level) throws SQLException {}@Overridepublic int getTransactionIsolation() throws SQLException {return 0;}@Overridepublic SQLWarning getWarnings() throws SQLException {return null;}@Overridepublic void clearWarnings() throws SQLException {}@Overridepublic Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {return null;}@Overridepublic PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {return null;}@Overridepublic CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {return null;}@Overridepublic Map<String, Class<?>> getTypeMap() throws SQLException {return null;}@Overridepublic void setTypeMap(Map<String, Class<?>> map) throws SQLException {}@Overridepublic void setHoldability(int holdability) throws SQLException {}@Overridepublic int getHoldability() throws SQLException {return 0;}@Overridepublic Savepoint setSavepoint() throws SQLException {return null;}@Overridepublic Savepoint setSavepoint(String name) throws SQLException {return null;}@Overridepublic void rollback(Savepoint savepoint) throws SQLException {}@Overridepublic void releaseSavepoint(Savepoint savepoint) throws SQLException {}@Overridepublic Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {return null;}@Overridepublic PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {return null;}@Overridepublic CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {return null;}@Overridepublic PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {return null;}@Overridepublic PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {return null;}@Overridepublic PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {return null;}@Overridepublic Clob createClob() throws SQLException {return null;}@Overridepublic Blob createBlob() throws SQLException {return null;}@Overridepublic NClob createNClob() throws SQLException {return null;}@Overridepublic SQLXML createSQLXML() throws SQLException {return null;}@Overridepublic boolean isValid(int timeout) throws SQLException {return false;}@Overridepublic void setClientInfo(String name, String value) throws SQLClientInfoException {}@Overridepublic void setClientInfo(Properties properties) throws SQLClientInfoException {}@Overridepublic String getClientInfo(String name) throws SQLException {return null;}@Overridepublic Properties getClientInfo() throws SQLException {return null;}@Overridepublic Array createArrayOf(String typeName, Object[] elements) throws SQLException {return null;}@Overridepublic Struct createStruct(String typeName, Object[] attributes) throws SQLException {return null;}@Overridepublic void setSchema(String schema) throws SQLException {}@Overridepublic String getSchema() throws SQLException {return null;}@Overridepublic void abort(Executor executor) throws SQLException {}@Overridepublic void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {}@Overridepublic int getNetworkTimeout() throws SQLException {return 0;}@Overridepublic <T> T unwrap(Class<T> iface) throws SQLException {return null;}@Overridepublic boolean isWrapperFor(Class<?> iface) throws SQLException {return false;}
}

连接池对象:

package com.semaphore;import java.sql.Connection;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;/*** 使用信号量控制数据库的链接和释放*/
public class DBPoolSemaphore {/*** 池容量*/private final static int POOL_SIZE = 10;/*** useful 代表可用连接* useless 代表已用连接* 为什么要使用两个Semaphore呢?是因为,在连接池中不只有连接本身是资源,空位也是资源,也需要记录*/private final Semaphore useful, useless;/*** 连接池*/private final static LinkedList<Connection> POOL = new LinkedList<>();/*** 使用静态块初始化池*/static {for (int i = 0; i < POOL_SIZE; i++) {POOL.addLast(SqlConnection.fetchConnection());}}public DBPoolSemaphore() {// 初始可用的许可证等于池容量useful = new Semaphore(POOL_SIZE);// 初始不可用的许可证容量为0useless = new Semaphore(0);}/*** 获取数据库连接** @return 连接对象*/public Connection takeConnection() throws InterruptedException {// 可用许可证减一useful.acquire();Connection connection;synchronized (POOL) {connection = POOL.removeFirst();}// 不可用许可证数量加一useless.release();return connection;}/*** 释放链接** @param connection 连接对象*/public void returnConnection(Connection connection) throws InterruptedException {if (null != connection) {// 打印日志System.out.println("当前有" + useful.getQueueLength() + "个线程等待获取连接," + "可用连接有" + useful.availablePermits() + "个");// 不可用许可证减一useless.acquire();synchronized (POOL) {POOL.addLast(connection);}// 可用许可证加一useful.release();}}
}

测试类:

package com.semaphore;import com.utils.SleepTools;import java.sql.Connection;
import java.util.Random;/*** 测试Semaphore*/
public class UseSemaphore {/*** 连接池*/public static final DBPoolSemaphore pool = new DBPoolSemaphore();private static class BusiThread extends Thread {@Overridepublic void run() {// 随机数工具类 为了让每个线程持有连接的时间不一样Random random = new Random();long start = System.currentTimeMillis();try {Connection connection = pool.takeConnection();System.out.println("Thread_" + Thread.currentThread().getId() + "_获取数据库连接耗时[" + (System.currentTimeMillis() - start) + "]ms.");// 模拟使用连接查询数据SleepTools.ms(100 + random.nextInt(100));System.out.println("查询数据完成归还连接");pool.returnConnection(connection);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {for (int i = 0; i < 50; i++) {BusiThread busiThread = new BusiThread();busiThread.start();}}
}

测试返回结果:

# 程序输出
Thread_12_获取数据库连接耗时[0]ms.
Thread_16_获取数据库连接耗时[0]ms.
Thread_13_获取数据库连接耗时[0]ms.
Thread_14_获取数据库连接耗时[0]ms.
Thread_15_获取数据库连接耗时[0]ms.
Thread_18_获取数据库连接耗时[0]ms.
Thread_17_获取数据库连接耗时[0]ms.
Thread_19_获取数据库连接耗时[0]ms.
Thread_42_获取数据库连接耗时[0]ms.
Thread_43_获取数据库连接耗时[0]ms.
查询数据完成归还连接
当前有40个线程等待获取连接,可用连接有0个
Thread_20_获取数据库连接耗时[107]ms.
查询数据完成归还连接
当前有39个线程等待获取连接,可用连接有0个
Thread_40_获取数据库连接耗时[107]ms.
查询数据完成归还连接
当前有38个线程等待获取连接,可用连接有0个
Thread_28_获取数据库连接耗时[127]ms.
查询数据完成归还连接
当前有37个线程等待获取连接,可用连接有0个
Thread_29_获取数据库连接耗时[130]ms.
查询数据完成归还连接
当前有36个线程等待获取连接,可用连接有0个
Thread_22_获取数据库连接耗时[134]ms.
查询数据完成归还连接
当前有35个线程等待获取连接,可用连接有0个
Thread_23_获取数据库连接耗时[153]ms.
查询数据完成归还连接
当前有34个线程等待获取连接,可用连接有0个
Thread_26_获取数据库连接耗时[159]ms.
查询数据完成归还连接
当前有33个线程等待获取连接,可用连接有0个
Thread_27_获取数据库连接耗时[174]ms.
查询数据完成归还连接
当前有32个线程等待获取连接,可用连接有0个
Thread_30_获取数据库连接耗时[179]ms.
查询数据完成归还连接
当前有31个线程等待获取连接,可用连接有0个
Thread_31_获取数据库连接耗时[185]ms.
查询数据完成归还连接
当前有30个线程等待获取连接,可用连接有0个
Thread_34_获取数据库连接耗时[214]ms.
查询数据完成归还连接
当前有29个线程等待获取连接,可用连接有0个
Thread_35_获取数据库连接耗时[238]ms.
查询数据完成归还连接
当前有28个线程等待获取连接,可用连接有0个
Thread_32_获取数据库连接耗时[244]ms.
查询数据完成归还连接
当前有27个线程等待获取连接,可用连接有0个
Thread_38_获取数据库连接耗时[260]ms.
查询数据完成归还连接
当前有26个线程等待获取连接,可用连接有0个
Thread_39_获取数据库连接耗时[270]ms.
查询数据完成归还连接
当前有25个线程等待获取连接,可用连接有0个
Thread_46_获取数据库连接耗时[295]ms.
查询数据完成归还连接
当前有24个线程等待获取连接,可用连接有0个
Thread_21_获取数据库连接耗时[322]ms.
查询数据完成归还连接
当前有23个线程等待获取连接,可用连接有0个
Thread_24_获取数据库连接耗时[333]ms.
查询数据完成归还连接
当前有22个线程等待获取连接,可用连接有0个
Thread_33_获取数据库连接耗时[357]ms.
查询数据完成归还连接
当前有21个线程等待获取连接,可用连接有0个
Thread_47_获取数据库连接耗时[367]ms.
查询数据完成归还连接
当前有20个线程等待获取连接,可用连接有0个
Thread_36_获取数据库连接耗时[370]ms.
查询数据完成归还连接
当前有19个线程等待获取连接,可用连接有0个
Thread_37_获取数据库连接耗时[374]ms.
查询数据完成归还连接
当前有18个线程等待获取连接,可用连接有0个
Thread_25_获取数据库连接耗时[392]ms.
查询数据完成归还连接
当前有17个线程等待获取连接,可用连接有0个
Thread_41_获取数据库连接耗时[404]ms.
查询数据完成归还连接
当前有16个线程等待获取连接,可用连接有0个
Thread_44_获取数据库连接耗时[416]ms.
查询数据完成归还连接
当前有15个线程等待获取连接,可用连接有0个
Thread_45_获取数据库连接耗时[433]ms.
查询数据完成归还连接
当前有14个线程等待获取连接,可用连接有0个
Thread_48_获取数据库连接耗时[469]ms.
查询数据完成归还连接
当前有13个线程等待获取连接,可用连接有0个
Thread_50_获取数据库连接耗时[482]ms.
查询数据完成归还连接
当前有12个线程等待获取连接,可用连接有0个
Thread_49_获取数据库连接耗时[514]ms.
查询数据完成归还连接
当前有11个线程等待获取连接,可用连接有0个
Thread_52_获取数据库连接耗时[523]ms.
查询数据完成归还连接
当前有10个线程等待获取连接,可用连接有0个
Thread_51_获取数据库连接耗时[533]ms.
查询数据完成归还连接
当前有9个线程等待获取连接,可用连接有0个
Thread_53_获取数据库连接耗时[540]ms.
查询数据完成归还连接
当前有8个线程等待获取连接,可用连接有0个
Thread_56_获取数据库连接耗时[561]ms.
查询数据完成归还连接
当前有7个线程等待获取连接,可用连接有0个
Thread_57_获取数据库连接耗时[574]ms.
查询数据完成归还连接
当前有6个线程等待获取连接,可用连接有0个
Thread_54_获取数据库连接耗时[591]ms.
查询数据完成归还连接
当前有5个线程等待获取连接,可用连接有0个
Thread_55_获取数据库连接耗时[605]ms.
查询数据完成归还连接
当前有4个线程等待获取连接,可用连接有0个
Thread_60_获取数据库连接耗时[615]ms.
查询数据完成归还连接
当前有3个线程等待获取连接,可用连接有0个
Thread_58_获取数据库连接耗时[663]ms.
查询数据完成归还连接
当前有2个线程等待获取连接,可用连接有0个
Thread_59_获取数据库连接耗时[667]ms.
查询数据完成归还连接
当前有1个线程等待获取连接,可用连接有0个
Thread_61_获取数据库连接耗时[677]ms.
查询数据完成归还连接
当前有0个线程等待获取连接,可用连接有0个
查询数据完成归还连接
当前有0个线程等待获取连接,可用连接有1个
查询数据完成归还连接
当前有0个线程等待获取连接,可用连接有2个
查询数据完成归还连接
当前有0个线程等待获取连接,可用连接有3个
查询数据完成归还连接
当前有0个线程等待获取连接,可用连接有4个
查询数据完成归还连接
当前有0个线程等待获取连接,可用连接有5个
查询数据完成归还连接
当前有0个线程等待获取连接,可用连接有6个
查询数据完成归还连接
当前有0个线程等待获取连接,可用连接有7个
查询数据完成归还连接
当前有0个线程等待获取连接,可用连接有8个
查询数据完成归还连接
当前有0个线程等待获取连接,可用连接有9个

通过执行结果可以很明确的看到,一上来就有10个线程获取到了连接,然后后面的40个线程进入阻塞,然后只有

释放链接之后,等待的线程就会有一个拿到,然后越后面的线程等待的时间就越长,然后一直到所有的线程执行

完毕,最后打印的可用连接有九个不是因为少了一个是因为在释放之前打印的,不是错误。

从结果中可以看到,我们对连接池中的资源的到了控制,这就是信号量的流量控制。

2.4 Exchanger

Exchanger,俗称交换器,用于在线程之间交换数据,但是比较受限,因为只能两个线程之间交换数据。

public Exchanger() {participant = new Participant();
}

这个构造函数没有什么好说的,也没有入参,只有在创建的时候指定一下需要交换的数据的泛型即可:

package com.exchanger;import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Exchanger;/*** 线程之间交换数据*/
public class UseExchange {private static final Exchanger<Set<String>> exchanger = new Exchanger<>();public static void main(String[] args) {new Thread() {@Overridepublic void run() {Set<String> aSet = new HashSet<>();aSet.add("A");aSet.add("B");aSet.add("C");try {Set<String> exchange = exchanger.exchange(aSet);for (String s : exchange) {System.out.println("aSet" + s);}} catch (InterruptedException e) {e.printStackTrace();}}}.start();new Thread() {@Overridepublic void run() {Set<String> bSet = new HashSet<>();bSet.add("1");bSet.add("2");bSet.add("3");try {Set<String> exchange = exchanger.exchange(bSet);for (String s : exchange) {System.out.println("bSet" + s);}} catch (InterruptedException e) {e.printStackTrace();}}}.start();}}

执行结果:

# 程序输出
aSet1
aSet2
aSet3
bSetA
bSetB
bSetC

通过执行结果可以清晰的看到,两个线程中的数据发生了交换,这就是 Exchanger 的线程数据交换了。

以上就是 JUC 的4大常用并发工具类了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/584250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Java车间工时管理系统(源码+部署文档)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

《新传奇》期刊投稿论文发表

《新传奇》杂志是经国家新闻出版总署批准、面向国内外公开发行的综合性社科期刊&#xff0c;由湖北省文联主管&#xff0c;湖北今古传奇传媒集团有限公司主办&#xff0c;湖北优秀期刊。本刊旨在坚守初心、引领创新&#xff0c;展示高水平研究成果&#xff0c;支持优秀学术人才…

如何使用 NFTScan NFT API 在 Gnosis 网络上开发 Web3 应用

Gnosis Chain 是一个兼容 EVM 的区块链&#xff0c;专注于快速且低成本的交易功能&#xff0c;采用独特的双通证模型&#xff1b;xDai 是一种用于交易、支付和手续费的稳定币&#xff0c;权益证明&#xff08;PoS&#xff09;保护将由 GNO 通过共识层 Gnosis Beacon Chain 提供…

【JVM】虚拟机的组成+字节码文件组成+类的生命周期

什么是JVM&#xff1f; JVM 本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件。 JVM的功能 1.解释和运行&#xff1a;对字节码文件中的指令实时的解释成机器码让计算机执行。 2.内存管理&#xff1a;自动为对象、方法等分配内存&#xff0c;自动…

【C++干货铺】STL中set和map的介绍和使用

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 序列式容器 关联式容器 键值对 树形结构的关联式容器 set set的介绍 set的使用 set的模板参数列表 set的构造 ​编辑 set的容量 set的删除和查找 mult…

web等保评测需要实机查看的操作系统、服务器、数据库和应用部分

“等保测评”全称是信息安全等级保护测评。是经公安部认证的具有资质的测评机构&#xff0c;依据国家信息安全等级保护规范规定&#xff0c;受有关单位委托&#xff0c;按照有关管理规范和技术标准&#xff0c;对信息系统安全等级保护状况进行检测评估的活动。 本文陆续将遇到的…

2023“SEED”第四届江苏大数据--新能源赛道 复赛Btop2总结

第一名是真的强&#xff01;基本都是第一&#xff0c;难以撼动。 昨天新能源赛道终于落下了帷幕&#xff0c;真的不是一般的卷。最后的排名都到了0.0几分的差距。跟队友很辛运复赛B榜单目前进入top3的行列&#xff0c;下面简单总结一下赛事过程。 初赛按照天级别预测未来一周各…

Linux iptables防火墙(一)

1.1 Linux防火墙基础 在 Internet 中&#xff0c;企业通过架设各种应用系统来为用户提供各种网络服务&#xff0c;如 Web 网站、 电子邮件系统、 FTP 服务器、数据库系统等。那么&#xff0c;如何来保护这些服务器&#xff0c;过滤企业不 需要的访问甚至是恶意的入侵呢&a…

算法设计与分析实验报告-贪心算法

校课程的简单实验报告。 算法设计与分析实验报告-递归与分治策略 算法设计与分析实验报告-动态规划算法 算法设计与分析实验报告-贪心算法 dijkstra迪杰斯特拉算法&#xff08;邻接表法&#xff09; 算法设计与分析实验报告-回溯法 算法设计与分析实验报告-分支限界法 …

halcon字符识别结果为“\x1A”

最近在做OCR字符识别&#xff0c;遇到了点小问题&#xff0c;记录一下。 由于是项目初期&#xff0c;所以我就打算调halcon自带库去识别一下看看效果如何&#xff0c;结果分类器的结果显示为“\x1A”。如下图 百度搜了一圈没有这个解答&#xff0c;所以就在halcon帮助文档里…

AtCoder Beginner Contest 334 G

G.Christmas Color Grid 2&#xff08;枚举&#xff0c;Tarjan&#xff09; 题意&#xff1a; 本题与问题 E E E类似。有一个 H H H行和 W W W列的网格&#xff0c;每个单元格都被涂成红色或绿色。用 ( i , j ) (i,j) (i,j)表示从上到下第 i i i行、从左到右第 j j j列的单元…

【LeetCode】修炼之路-0001-Two Sum(两数之和)【python】【简单】

前言 计算机科学作为一门实践性极强的学科,代码能力的培养尤为重要。当前网络上有非常多优秀的前辈分享了LeetCode的最佳算法题解,这对于我们这些初学者来说提供了莫大的帮助,但对于我这种缺乏编程直觉的学习者而言,这往往难以消化吸收。&#xff08;为什么别人就能想出这么优雅…

《异常检测——从经典算法到深度学习》25 基于深度隔离林的异常检测算法

《异常检测——从经典算法到深度学习》 0 概论1 基于隔离森林的异常检测算法 2 基于LOF的异常检测算法3 基于One-Class SVM的异常检测算法4 基于高斯概率密度异常检测算法5 Opprentice——异常检测经典算法最终篇6 基于重构概率的 VAE 异常检测7 基于条件VAE异常检测8 Donut: …

一篇文章掌握 NestJS 所有的生命周期以及生命周期的执行时机

前言 NestJS 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的框架&#xff0c;它使用 TypeScript 作为开发语言&#xff0c;也支持原生的 JavaScript。在 NestJS 中&#xff0c;生命周期事件是一个重要的概念。在我们构建和管理应用程序时&#xff0c;有时需要在特定…

Prometheus快速入门实战

Prometheus快速入门实战 1. 介绍 prometheus受启发于Google的Brogmon监控系统&#xff08;相似kubernetes是从Brog系统演变而来&#xff09;。 2016年5月继kubernetes之后成为第二个加入CNCF基金会的项目&#xff0c;同年6月正式发布1.0版本。2017年底发布基于全新存储层的2.…

【数据结构】C语言实现双链表的基本操作

双链表及其基本操作的实现 导言一、单链表与双链表二、双链表类型的创建三、双链表的初始化四、双链表的创建五、双链表的遍历六、双链表的查找七、双链表的插入八、双链表的删除结语 导言 大家好&#xff0c;很高兴又和大家见面啦&#xff01;&#xff01;&#xff01; 经过…

反射讲解(有图有真相)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、反射是什么&#xff1f;二、反射有啥好处&#xff1f;1. 没反射2. 有反射 三、反射的常用方法1. 获取 Class 对象&#xff1a;2. 获取类的构造方法&#xf…

数组的声明

概要&#xff1a; 数组的声明分为三个部分 第一部分&#xff1a;数组中元素的数据类型 第二部分&#xff1a;数组名 第三部分&#xff1a;数组标识符(方括号)和数组大小 一、测试代码 #include<stdio.h> int main() {int arr_int[10];char* arr_str[10];arr_in…

软件测试/测试开发丨Selenium环境安装配置

一、selenium 环境配置 1、下载浏览器 目前比较常用的浏览器是 Google Chrome 浏览器&#xff0c;所以本教程以 chrome 为主&#xff0c;后面简介一下其他浏览器的环境配置。 chrome 下载: www.google.cn/chrome/ 2、chromedriver 环境配置 chromedriver 是chromedriver提…

【C++】引用详解

前言 在学习C语言时&#xff0c;我们通常会遇到两个数交换的问题&#xff0c;为了实现这一功能&#xff0c;我们会编写一个经典的Swap函数&#xff0c;如下所示&#xff1a; void Swap(int *a, int *b) {int tmp *a;*a *b;*b tmp; } 然而&#xff0c;这个Swap函数看起来可…