Java

什么是JUC?

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

4大常用并发工具类

CountDownLatch
CyclicBarrier
Semaphore
ExChanger

CountDownLatch

CountDownLatch,俗称闭锁,作用是类似加强版的Join,是让一组线程等待其他的线程完成工作以后才执行
就比如在启动框架服务的时候,主线程需要在环境线程初始化完成之后才能启动,这时候就可以实现使用CountDownLatch来完成

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

在源码中可以看到,创建CountDownLatch时,需要传入一个int类型的参数,将决定在执行次扣减之后,等待的线程被唤醒
image.png
通过这个类图就可以知道其实CountDownLatch并没有多少东西
方法介绍:
CountDownLatch:初始化方法
await:等待方法,同时带参数的是超时重载方法
countDown:每执行一次,计数器减一,就是初始化传入的数字,也代表着一个线程完成了任务
getCount:获取当前值
toString:这个就不用说了
里面的Sync是一个内部类,外面的方法其实都是操作这个内部类的,这个内部类继承了AQS,实现的标准方法,AQS将在后面的章节写
2021-09-17-12-13-17-488745.png
主线程中创建CountDownLatch(3),然后主线程await阻塞,然后线程A,B,C各自完成了任务,调用了countDown,之后,每个线程调用一次计数器就会减一,初始是3,然后A线程调用后变成2,B线程调用后变成1,C线程调用后,变成0,这时就会唤醒正在await的主线程,然后主线程继续执行
上代码:休眠工具类,之后的代码都会用到

  1. import java.util.concurrent.TimeUnit;
  2. /**
  3. * 类说明:线程休眠辅助工具类
  4. */
  5. public class SleepTools {
  6. /**
  7. * 按秒休眠
  8. * @param seconds 秒数
  9. */
  10. public static final void second(int seconds) {
  11. try {
  12. TimeUnit.SECONDS.sleep(seconds);
  13. } catch (InterruptedException e) {
  14. }
  15. }
  16. /**
  17. * 按毫秒数休眠
  18. * @param seconds 毫秒数
  19. */
  20. public static final void ms(int seconds) {
  21. try {
  22. TimeUnit.MILLISECONDS.sleep(seconds);
  23. } catch (InterruptedException e) {
  24. }
  25. }
  26. }
  1. import org.dance.tools.SleepTools;
  2. import java.util.concurrent.CountDownLatch;
  3. /**
  4. * CountDownLatch的使用,有五个线程,6个扣除点
  5. * 扣除完成后主线程和业务线程,才能执行工作
  6. * 扣除点一般都是大于等于需要初始化的线程的
  7. */
  8. public class UseCountDownLatch {
  9. /**
  10. * 设置为6个扣除点
  11. */
  12. static CountDownLatch countDownLatch = new CountDownLatch(6);
  13. /**
  14. * 初始化线程
  15. */
  16. private static class InitThread implements Runnable {
  17. @Override
  18. public void run() {
  19. System.out.println("thread_" + Thread.currentThread().getId() + " ready init work .....");
  20. // 执行扣减 扣减不代表结束
  21. countDownLatch.countDown();
  22. for (int i = 0; i < 2; i++) {
  23. System.out.println("thread_" + Thread.currentThread().getId() + ".....continue do its work");
  24. }
  25. }
  26. }
  27. /**
  28. * 业务线程
  29. */
  30. private static class BusiThread implements Runnable {
  31. @Override
  32. public void run() {
  33. // 业务线程需要在等初始化完毕后才能执行
  34. try {
  35. countDownLatch.await();
  36. for (int i = 0; i < 3; i++) {
  37. System.out.println("BusiThread " + Thread.currentThread().getId() + " do business-----");
  38. }
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. }
  44. public static void main(String[] args) {
  45. // 创建单独的初始化线程
  46. new Thread(){
  47. @Override
  48. public void run() {
  49. SleepTools.ms(1);
  50. System.out.println("thread_" + Thread.currentThread().getId() + " ready init work step 1st.....");
  51. // 扣减一次
  52. countDownLatch.countDown();
  53. System.out.println("begin stop 2nd.....");
  54. SleepTools.ms(1);
  55. System.out.println("thread_" + Thread.currentThread().getId() + " ready init work step 2nd.....");
  56. // 扣减一次
  57. countDownLatch.countDown();
  58. }
  59. }.start();
  60. // 启动业务线程
  61. new Thread(new BusiThread()).start();
  62. // 启动初始化线程
  63. for (int i = 0; i <= 3; i++) {
  64. new Thread(new InitThread()).start();
  65. }
  66. // 主线程进入等待
  67. try {
  68. countDownLatch.await();
  69. System.out.println("Main do ites work.....");
  70. } catch (InterruptedException e) {
  71. e.printStackTrace();
  72. }
  73. }
  74. }

返回结果:

  1. thread_13 ready init work .....
  2. thread_13.....continue do its work
  3. thread_13.....continue do its work
  4. thread_14 ready init work .....
  5. thread_14.....continue do its work
  6. thread_14.....continue do its work
  7. thread_15 ready init work .....
  8. thread_15.....continue do its work
  9. thread_11 ready init work step 1st.....
  10. begin stop 2nd.....
  11. thread_16 ready init work .....
  12. thread_16.....continue do its work
  13. thread_16.....continue do its work
  14. thread_15.....continue do its work
  15. thread_11 ready init work step 2nd.....
  16. Main do ites work.....
  17. BusiThread 12 do business-----
  18. BusiThread 12 do business-----
  19. BusiThread 12 do business-----

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

CyclicBarrier

CyclicBarrier,俗称栅栏锁,作用是让一组线程到达某个屏障,被阻塞,一直到组内的最后一个线程到达,然后屏障开放,接着,所有的线程继续运行
这个感觉和CountDownLatch有点相似,但是其实是不一样的,所谓的差别,将在下面详解
CyclicBarrier的构造参数有两个

  1. /**
  2. * Creates a new {@code CyclicBarrier} that will trip when the
  3. * given number of parties (threads) are waiting upon it, and
  4. * does not perform a predefined action when the barrier is tripped.
  5. *
  6. * @param parties the number of threads that must invoke {@link #await}
  7. * before the barrier is tripped
  8. * @throws IllegalArgumentException if {@code parties} is less than 1
  9. */
  10. public CyclicBarrier(int parties) {
  11. this(parties, null);
  12. }
  13. /**
  14. * Creates a new {@code CyclicBarrier} that will trip when the
  15. * given number of parties (threads) are waiting upon it, and which
  16. * will execute the given barrier action when the barrier is tripped,
  17. * performed by the last thread entering the barrier.
  18. *
  19. * @param parties the number of threads that must invoke {@link #await}
  20. * before the barrier is tripped
  21. * @param barrierAction the command to execute when the barrier is
  22. * tripped, or {@code null} if there is no action
  23. * @throws IllegalArgumentException if {@code parties} is less than 1
  24. */
  25. public CyclicBarrier(int parties, Runnable barrierAction) {
  26. if (parties <= 0) throw new IllegalArgumentException();
  27. this.parties = parties;
  28. this.count = parties;
  29. this.barrierCommand = barrierAction;
  30. }

很明显能感觉出来,上面的构造参数调用了下面的构造参数,是一个构造方法重载
首先这个第一个参数也树Int类型的,传入的是执行线程的个数,这个数量和CountDownLatch不一样,这个数量是需要和线程数量吻合的,CountDownLatch则不一样,CountDownLatch可以大于等于,而CyclicBarrier只能等于,然后是第二个参数,第二个参数是barrierAction,这个参数是当屏障开放后,执行的任务线程,如果当屏障开放后需要执行什么任务,可以写在这个线程中
2021-09-17-12-13-17-671753.png
主线程创建CyclicBarrier(3,barrierAction),然后由线程开始执行,线程A,B执行完成后都调用了await,然后他们都在一个屏障前阻塞者,需要等待线程C也,执行完成,调用await之后,然后三个线程都达到屏障后,屏障开放,然后线程继续执行,并且barrierAction在屏障开放的一瞬间也开始执行
上代码:

  1. import org.dance.tools.SleepTools;
  2. import java.util.Map;
  3. import java.util.Random;
  4. import java.util.concurrent.BrokenBarrierException;
  5. import java.util.concurrent.ConcurrentHashMap;
  6. import java.util.concurrent.CyclicBarrier;
  7. /**
  8. * CyclicBarrier的使用
  9. */
  10. public class UseCyclicBarrier {
  11. /**
  12. * 存放子线程工作结果的安全容器
  13. */
  14. private static ConcurrentHashMap<String, Long> resultMap = new ConcurrentHashMap<>();
  15. private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5,new CollectThread());
  16. /**
  17. * 结果打印线程
  18. * 用来演示CyclicBarrier的第二个参数,barrierAction
  19. */
  20. private static class CollectThread implements Runnable {
  21. @Override
  22. public void run() {
  23. StringBuffer result = new StringBuffer();
  24. for (Map.Entry<String, Long> workResult : resultMap.entrySet()) {
  25. result.append("[" + workResult.getValue() + "]");
  26. }
  27. System.out.println("the result = " + result);
  28. System.out.println("do other business.....");
  29. }
  30. }
  31. /**
  32. * 工作子线程
  33. * 用于CyclicBarrier的一组线程
  34. */
  35. private static class SubThread implements Runnable {
  36. @Override
  37. public void run() {
  38. // 获取当前线程的ID
  39. long id = Thread.currentThread().getId();
  40. // 放入统计容器中
  41. resultMap.put(String.valueOf(id), id);
  42. Random random = new Random();
  43. try {
  44. if (random.nextBoolean()) {
  45. Thread.sleep(1000 + id);
  46. System.out.println("Thread_"+id+"..... do something");
  47. }
  48. System.out.println(id+" is await");
  49. cyclicBarrier.await();
  50. Thread.sleep(1000+id);
  51. System.out.println("Thread_"+id+".....do its business");
  52. } catch (InterruptedException e) {
  53. e.printStackTrace();
  54. } catch (BrokenBarrierException e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. }
  59. public static void main(String[] args) {
  60. for (int i = 0; i <= 4; i++) {
  61. Thread thread = new Thread(new SubThread());
  62. thread.start();
  63. }
  64. }
  65. }

返回结果:

  1. 11 is await
  2. 14 is await
  3. 15 is await
  4. Thread_12..... do something
  5. 12 is await
  6. Thread_13..... do something
  7. 13 is await
  8. the result = [11][12][13][14][15]
  9. do other business.....
  10. Thread_11.....do its business
  11. Thread_12.....do its business
  12. Thread_13.....do its business
  13. Thread_14.....do its business
  14. Thread_15.....do its business

通过返回结果可以看出前面的11 14 15三个线程没有进入if语句块,在执行到await的时候进入了等待,而另外12 13两个线程进入到了if语句块当中,多休眠了1秒多,然后当5个线程同时到达await的时候,屏障开放,执行了barrierAction线程,然后线程组继续执行
解释一下CountDownLatchCyclicBarrier的区别吧!
首先就是CountDownLatch的构造参数传入的数量一般都是大于等于线程,数量的,因为他是有第三方控制的,可以扣减多次,然后就是CyclicBarrier的构造参数第一个参数传入的数量一定是等于线程的个数的,因为他是由一组线程自身控制的
区别

CountDownLatch CyclicBarrier
控制 第三方控制 自身控制
传入数量 大于等于线程数量 等于线程数量

Semaphore

Semaphore,俗称信号量,作用于控制同时访问某个特定资源的线程数量,用在流量控制
一说特定资源控制,那么第一时间就想到了数据库连接..
之前用等待超时模式写了一个数据库连接池,打算用这个Semaphone也写一个

  1. /**
  2. * Creates a {@code Semaphore} with the given number of
  3. * permits and nonfair fairness setting.
  4. *
  5. * @param permits the initial number of permits available.
  6. * This value may be negative, in which case releases
  7. * must occur before any acquires will be granted.
  8. */
  9. public Semaphore(int permits) {
  10. sync = new NonfairSync(permits);
  11. }

在源码中可以看到在构建Semaphore信号量的时候,需要传入许可证的数量,这个数量就是资源的最大允许的访问的线程数
接下里用信号量实现一个数据库连接池
连接对象

  1. import org.dance.tools.SleepTools;
  2. import java.sql.*;
  3. import java.util.Map;
  4. import java.util.Properties;
  5. import java.util.concurrent.Executor;
  6. /**
  7. * 数据库连接
  8. */
  9. public class SqlConnection implements Connection {
  10. /**
  11. * 获取数据库连接
  12. * @return
  13. */
  14. public static final Connection fetchConnection(){
  15. return new SqlConnection();
  16. }
  17. @Override
  18. public void commit() throws SQLException {
  19. SleepTools.ms(70);
  20. }
  21. @Override
  22. public Statement createStatement() throws SQLException {
  23. SleepTools.ms(1);
  24. return null;
  25. }
  26. @Override
  27. public PreparedStatement prepareStatement(String sql) throws SQLException {
  28. return null;
  29. }
  30. @Override
  31. public CallableStatement prepareCall(String sql) throws SQLException {
  32. return null;
  33. }
  34. @Override
  35. public String nativeSQL(String sql) throws SQLException {
  36. return null;
  37. }
  38. @Override
  39. public void setAutoCommit(boolean autoCommit) throws SQLException {
  40. }
  41. @Override
  42. public boolean getAutoCommit() throws SQLException {
  43. return false;
  44. }
  45. @Override
  46. public void rollback() throws SQLException {
  47. }
  48. @Override
  49. public void close() throws SQLException {
  50. }
  51. @Override
  52. public boolean isClosed() throws SQLException {
  53. return false;
  54. }
  55. @Override
  56. public DatabaseMetaData getMetaData() throws SQLException {
  57. return null;
  58. }
  59. @Override
  60. public void setReadOnly(boolean readOnly) throws SQLException {
  61. }
  62. @Override
  63. public boolean isReadOnly() throws SQLException {
  64. return false;
  65. }
  66. @Override
  67. public void setCatalog(String catalog) throws SQLException {
  68. }
  69. @Override
  70. public String getCatalog() throws SQLException {
  71. return null;
  72. }
  73. @Override
  74. public void setTransactionIsolation(int level) throws SQLException {
  75. }
  76. @Override
  77. public int getTransactionIsolation() throws SQLException {
  78. return 0;
  79. }
  80. @Override
  81. public SQLWarning getWarnings() throws SQLException {
  82. return null;
  83. }
  84. @Override
  85. public void clearWarnings() throws SQLException {
  86. }
  87. @Override
  88. public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
  89. return null;
  90. }
  91. @Override
  92. public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
  93. return null;
  94. }
  95. @Override
  96. public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
  97. return null;
  98. }
  99. @Override
  100. public Map<String, Class<?>> getTypeMap() throws SQLException {
  101. return null;
  102. }
  103. @Override
  104. public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
  105. }
  106. @Override
  107. public void setHoldability(int holdability) throws SQLException {
  108. }
  109. @Override
  110. public int getHoldability() throws SQLException {
  111. return 0;
  112. }
  113. @Override
  114. public Savepoint setSavepoint() throws SQLException {
  115. return null;
  116. }
  117. @Override
  118. public Savepoint setSavepoint(String name) throws SQLException {
  119. return null;
  120. }
  121. @Override
  122. public void rollback(Savepoint savepoint) throws SQLException {
  123. }
  124. @Override
  125. public void releaseSavepoint(Savepoint savepoint) throws SQLException {
  126. }
  127. @Override
  128. public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
  129. return null;
  130. }
  131. @Override
  132. public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
  133. return null;
  134. }
  135. @Override
  136. public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
  137. return null;
  138. }
  139. @Override
  140. public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
  141. return null;
  142. }
  143. @Override
  144. public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
  145. return null;
  146. }
  147. @Override
  148. public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
  149. return null;
  150. }
  151. @Override
  152. public Clob createClob() throws SQLException {
  153. return null;
  154. }
  155. @Override
  156. public Blob createBlob() throws SQLException {
  157. return null;
  158. }
  159. @Override
  160. public NClob createNClob() throws SQLException {
  161. return null;
  162. }
  163. @Override
  164. public SQLXML createSQLXML() throws SQLException {
  165. return null;
  166. }
  167. @Override
  168. public boolean isValid(int timeout) throws SQLException {
  169. return false;
  170. }
  171. @Override
  172. public void setClientInfo(String name, String value) throws SQLClientInfoException {
  173. }
  174. @Override
  175. public void setClientInfo(Properties properties) throws SQLClientInfoException {
  176. }
  177. @Override
  178. public String getClientInfo(String name) throws SQLException {
  179. return null;
  180. }
  181. @Override
  182. public Properties getClientInfo() throws SQLException {
  183. return null;
  184. }
  185. @Override
  186. public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
  187. return null;
  188. }
  189. @Override
  190. public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
  191. return null;
  192. }
  193. @Override
  194. public void setSchema(String schema) throws SQLException {
  195. }
  196. @Override
  197. public String getSchema() throws SQLException {
  198. return null;
  199. }
  200. @Override
  201. public void abort(Executor executor) throws SQLException {
  202. }
  203. @Override
  204. public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
  205. }
  206. @Override
  207. public int getNetworkTimeout() throws SQLException {
  208. return 0;
  209. }
  210. @Override
  211. public <T> T unwrap(Class<T> iface) throws SQLException {
  212. return null;
  213. }
  214. @Override
  215. public boolean isWrapperFor(Class<?> iface) throws SQLException {
  216. return false;
  217. }
  218. }

连接池对象

  1. import java.sql.Connection;
  2. import java.util.ArrayList;
  3. import java.util.HashSet;
  4. import java.util.Iterator;
  5. import java.util.LinkedList;
  6. import java.util.concurrent.Semaphore;
  7. /**
  8. * 使用信号量控制数据库的链接和释放
  9. */
  10. public class DBPoolSemaphore {
  11. /**
  12. * 池容量
  13. */
  14. private final static int POOL_SIZE = 10;
  15. /**
  16. * useful 代表可用连接
  17. * useless 代表已用连接
  18. * 为什么要使用两个Semaphore呢?是因为,在连接池中不只有连接本身是资源,空位也是资源,也需要记录
  19. */
  20. private final Semaphore useful, useless;
  21. /**
  22. * 连接池
  23. */
  24. private final static LinkedList<Connection> POOL = new LinkedList<>();
  25. /**
  26. * 使用静态块初始化池
  27. */
  28. static {
  29. for (int i = 0; i < POOL_SIZE; i++) {
  30. POOL.addLast(SqlConnection.fetchConnection());
  31. }
  32. }
  33. public DBPoolSemaphore() {
  34. // 初始可用的许可证等于池容量
  35. useful = new Semaphore(POOL_SIZE);
  36. // 初始不可用的许可证容量为0
  37. useless = new Semaphore(0);
  38. }
  39. /**
  40. * 获取数据库连接
  41. *
  42. * @return 连接对象
  43. */
  44. public Connection takeConnection() throws InterruptedException {
  45. // 可用许可证减一
  46. useful.acquire();
  47. Connection connection;
  48. synchronized (POOL) {
  49. connection = POOL.removeFirst();
  50. }
  51. // 不可用许可证数量加一
  52. useless.release();
  53. return connection;
  54. }
  55. /**
  56. * 释放链接
  57. *
  58. * @param connection 连接对象
  59. */
  60. public void returnConnection(Connection connection) throws InterruptedException {
  61. if(null!=connection){
  62. // 打印日志
  63. System.out.println("当前有"+useful.getQueueLength()+"个线程等待获取连接,,"
  64. +"可用连接有"+useful.availablePermits()+"个");
  65. // 不可用许可证减一
  66. useless.acquire();
  67. synchronized (POOL){
  68. POOL.addLast(connection);
  69. }
  70. // 可用许可证加一
  71. useful.release();
  72. }
  73. }
  74. }

测试类:

  1. import org.dance.tools.SleepTools;
  2. import java.sql.Connection;
  3. import java.util.Random;
  4. /**
  5. * 测试Semaphore
  6. */
  7. public class UseSemaphore {
  8. /**
  9. * 连接池
  10. */
  11. public static final DBPoolSemaphore pool = new DBPoolSemaphore();
  12. private static class BusiThread extends Thread{
  13. @Override
  14. public void run() {
  15. // 随机数工具类 为了让每个线程持有连接的时间不一样
  16. Random random = new Random();
  17. long start = System.currentTimeMillis();
  18. try {
  19. Connection connection = pool.takeConnection();
  20. System.out.println("Thread_"+Thread.currentThread().getId()+
  21. "_获取数据库连接耗时["+(System.currentTimeMillis()-start)+"]ms.");
  22. // 模拟使用连接查询数据
  23. SleepTools.ms(100+random.nextInt(100));
  24. System.out.println("查询数据完成归还连接");
  25. pool.returnConnection(connection);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }
  31. public static void main(String[] args) {
  32. for (int i = 0; i < 50; i++) {
  33. BusiThread busiThread = new BusiThread();
  34. busiThread.start();
  35. }
  36. }
  37. }

测试返回结果:

  1. Thread_11_获取数据库连接耗时[0]ms.
  2. Thread_12_获取数据库连接耗时[0]ms.
  3. Thread_13_获取数据库连接耗时[0]ms.
  4. Thread_14_获取数据库连接耗时[0]ms.
  5. Thread_15_获取数据库连接耗时[0]ms.
  6. Thread_16_获取数据库连接耗时[0]ms.
  7. Thread_17_获取数据库连接耗时[0]ms.
  8. Thread_18_获取数据库连接耗时[0]ms.
  9. Thread_19_获取数据库连接耗时[0]ms.
  10. Thread_20_获取数据库连接耗时[0]ms.
  11. 查询数据完成归还连接
  12. 当前有40个线程等待获取连接,,可用连接有0
  13. Thread_21_获取数据库连接耗时[112]ms.
  14. 查询数据完成归还连接
  15. ...................
  16. 查询数据完成归还连接
  17. 当前有2个线程等待获取连接,,可用连接有0
  18. Thread_59_获取数据库连接耗时[637]ms.
  19. 查询数据完成归还连接
  20. 当前有1个线程等待获取连接,,可用连接有0
  21. Thread_60_获取数据库连接耗时[660]ms.
  22. 查询数据完成归还连接
  23. 当前有0个线程等待获取连接,,可用连接有0
  24. 查询数据完成归还连接
  25. ...................
  26. 当前有0个线程等待获取连接,,可用连接有8
  27. 查询数据完成归还连接
  28. 当前有0个线程等待获取连接,,可用连接有9

通过执行结果可以很明确的看到,一上来就有10个线程获取到了连接,,然后后面的40个线程进入阻塞,然后只有释放链接之后,等待的线程就会有一个拿到,然后越后面的线程等待的时间就越长,然后一直到所有的线程执行完毕
最后打印的可用连接有九个不是因为少了一个是因为在释放之前打印的,不是错误
从结果中可以看到,对连接池中的资源的到了控制,这就是信号量的流量控制

Exchanger

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

  1. /**
  2. * Creates a new Exchanger.
  3. */
  4. public Exchanger() {
  5. participant = new Participant();
  6. }

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

  1. import java.util.HashSet;
  2. import java.util.Set;
  3. import java.util.concurrent.Exchanger;
  4. /**
  5. * 线程之间交换数据
  6. */
  7. public class UseExchange {
  8. private static final Exchanger<Set<String>> exchanger = new Exchanger<>();
  9. public static void main(String[] args) {
  10. new Thread(){
  11. @Override
  12. public void run() {
  13. Set<String> aSet = new HashSet<>();
  14. aSet.add("A");
  15. aSet.add("B");
  16. aSet.add("C");
  17. try {
  18. Set<String> exchange = exchanger.exchange(aSet);
  19. for (String s : exchange) {
  20. System.out.println("aSet"+s);
  21. }
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }.start();
  27. new Thread(){
  28. @Override
  29. public void run() {
  30. Set<String> bSet = new HashSet<>();
  31. bSet.add("1");
  32. bSet.add("2");
  33. bSet.add("3");
  34. try {
  35. Set<String> exchange = exchanger.exchange(bSet);
  36. for (String s : exchange) {
  37. System.out.println("bSet"+s);
  38. }
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. }.start();
  44. }
  45. }

执行结果:

  1. bSetA
  2. bSetB
  3. bSetC
  4. aSet1
  5. aSet2
  6. aSet3

通过执行结果可以清晰的看到,两个线程中的数据发生了交换,这就是Exchanger的线程数据交换了
以上就是JUC的4大常用并发工具类了。