JUC(java.util.concurrent)要点笔记
wait | sleep | |
调用者 | Object对象 | Thread类静态调用 |
释放锁 | Y | N |
使用范围 | 与notify成组使用、用于线程通信 | 单独使用、哪里都可以用 |
异常捕获 | 可以不捕获 |
-
synchronized 和 Lock 区别:
synchronized | java.util.concurrent.locks.Lock | |
语法 | 关键字 | 接口 |
尝试获取锁 | N、若A获得则B一直等待 | Y、可尝试获取若失败则放弃 |
自动释放锁 | Y | N |
公平锁 | N | 默认非公平、可设置 |
精准控制 | N、适合代码量小的同步 | Y |
-
synchronized锁的是谁?static synchronized 或 synchronized(xxx.class) 锁的是class模板对象、否则锁的是调用者。
-
线程编码口诀:线程操作资源类
-
Callable接口与Runnable区别
synchronized Runnable 返回值 Y N 声明抛出异常 Y
注:当线程池中Callable任务运行异常时、异常会被抛到外部、外部可通过ExecutionException捕捉到N
注:当线程池中Runnable任务运行异常时、异常不会被抛到外部、需在run方法中处理
Future接口
方法 | 含义 |
get() | 获取结果(阻塞) |
get(long timeout, TimeUnit unit) | 在指定时间内获取结果(阻塞) |
isDone() | 任务是否已完成(非阻塞) |
cancel(boolean mayInterruptIfRunning) | 取消任务,并返回命令是否发送成功 注:只要发送成功就返回true,和任务是否确实被取消无关 |
任务是否已被取消 注: 1.当使用cancel(false)时,isCancelled()=true也不表示任务已中断; 2.当使用cancel(true)时,isCancelled()=true也不表示任务已中断,是否中断成功依赖于 if (Thread.currentThread().isInterrupted()) 代码块 |
-
线程之间通信:判断、执行、通知
-
虚假唤醒问题:使用while进行条件判断
-
生产者/消费者问题synchronized和JUC版实现对比:
synchronized | JUC版 | |
判断条件 | Object | Condition |
等待方法 | wait | await |
通知方法 | notify/notifyAll | signal/signalAll |
锁
读写锁:
独占锁(写锁):一次只能被一个线程占有
共享锁(读锁):该锁可以被多个线程占有
自旋锁
偏向锁
常用辅助类
Semaphore 信号量
用途:多线程共享资源争夺、并发线程数量控制、多生产者/多消费者模式;
注意事项:
-
创建对象时传入的permits仅仅是初值,可通过多次调用release动态增加permits
-
创建对象时可传参控制是否公平,默认为非公平
方法 | 可被中断 | 不会被中断 | 尝试获得(非阻塞) | 指定时间内尝试获得 |
获得 | acquire() acquire(int permits) |
acquireUninterruptibly() acquireUninterruptibly(int permits) |
tryAcquire() tryAcquire(int permits) |
tryAcquire(long timeout, TimeUnit unit) tryAcquire(int permits, long timeout, TimeUnit unit) |
释放 | release() release(int permits) |
其他方法:
方法 | 含义 |
availablePermits() | 当前可用许可数 |
drainPermits() | 获取并返回立即可用的许可数、并将可用许可数清零 |
getQueueLength() | 取得等待许可的线程数 |
hasQueuedThreads() | 判断是否有线程在等待许可 |
Exchanger 交换器
用途:2个线程之间传输数据
方法 | 说明 |
exchange(V x) | 没有其他线程来取数据则会阻塞 |
exchange(V x, long timeout, TimeUnit unit) | 指定时间内没有其他线程来取数据,则抛出java.util.concurrent.TimeoutException |
CountDownLatch 减法计数器
用途:控制线程间同步,当计数器变为0的时候继续运行,否则阻塞。
注意事项:计数器无法重置
方法 | 说明 | 抛出异常 |
await() await(long timeout, TimeUnit unit) |
进入等待状态 | InterruptedException |
countDown() | 计数器-1 |
CyclicBarrier 加法计数器
用途:控制线程间同步,当计数器达到指定值的时候继续运行,否则阻塞。
方法 | 说明 | 抛出异常 |
await() | 进入等待状态 | InterruptedException BrokenBarrierException |
await(long timeout, TimeUnit unit) | 进入等待状态有限时间 | InterruptedException BrokenBarrierException TimeoutException |
getNumberWaiting() | 返回已到达屏障点的线程数, 注意:最后一个线程已到达时会返回0; 即假设parties=3,线程1,2 await()方法之后调用getNumberWaiting()=1,2,而线程3 await()方法之后调用getNumberWaiting()=0 |
|
getParties() | 屏障对象个数 | |
reset() | 重置屏障,等待屏障的其他线程会出现BrokenBarrierException |
CountDownLatch和CyclicBarrier区别:
CountDownLatch使用情况为2个角色互等、CyclicBarrier使用情况为同类互等。
Phaser 移相器(CyclicBarrier增强版) since1.7
用途:CyclicBarrier增强版、可动态增减parties计数,可用于线程分组同步控制。
方法 | 说明 |
arrive() | 使getArrivedParties()+1,且不等待其他线程,并重置计数器 |
awaitAdvance(int phase) | 如果参数phase值和当前getPhase()方法返回值一致则等待,否则继续运行,类似旁观者作用,可被中断但不会影响其他线程运行,不抛出InterruptedException |
awaitAdvanceInterruptibly(int phase) | 如果参数phase值和当前getPhase()方法返回值一致则等待,否则继续运行,可被中断但不会影响其他线程运行,抛出InterruptedException |
awaitAdvanceInterruptibly(int phase, long timeout, TimeUnit unit) | 同上,timeout时会抛出TimeoutException |
arriveAndAwaitAdvance() | 计数不足时会阻塞 |
arriveAndDeregister() | 退出计数,并使parties-1 |
getPhase() | 获取已到达屏障的对象个数 |
getRegisteredParties() | 获取注册的parties数 |
register() | parties+1 |
bulkRegister(int parties) | 批量增加parties |
getArrivedParties() | 获取已经被使用的parties数 |
getUnarrivedParties() | 获取还未被使用的parties数 |
forceTermination() | 取消屏障,线程继续执行后续,不出现异常 |
isTerminated() | 屏障是否已取消 |
实际工作中不推荐直接使用:see《阿里巴巴Java开发手册(终极版)》一、编程规约-(六)并发处理-条目4
线程池大小/线程数
线程池大小与处理器的利用率之比估算公式:N-threads = N-CPU * U-CPU * (1 + W/C)
其中:
❑N-CPU是处理器的核的数目=Runtime.getRuntime().availableProcessors()
❑U-CPU是期望的CPU利用率(该值应该介于0和1之间)
❑W/C是等待时间与计算时间的比率
线程数设定:
-
CPU密集型(计算密集型):
最大线程数=CPU核数=Runtime.getRuntime().availableProcessors(); 推荐使用Stream接口。
-
IO密集型:
最大线程数=IO任务的倍数、不能低于IO任务的数量;使用CompletableFuture灵活性更好。
execute和submit方法区别
execute | submit | |
返回值 | N | Y |
异常 | 默认直接抛出、不能捕获,可通过ThreadFactory方式进行捕获 | 默认可通过catch (ExecutionException e)捕获 |
CompletionService
用途:避免FutureTask阻塞缺点,更有效地处理Future返回值。
方法 | 说明 |
take() | 获取已完成任务的Future(非阻塞),但当存在有任务未能完成时 take().get() 仍会阻塞 |
poll() | 获取并移除已完成任务的Future,不存在则返回null(非阻塞) |
poll(long timeout, TimeUnit unit) | 等待指定时间,无论结果如何均往下执行 |
2步骤:任务拆分、结果合并。
应用场景:大数据量,好处:提高效率、坏处:产生资源争夺
原理:工作窃取(work-stealing)算法、底层使用双端队列
不安全集合类 | 安全集合类 |
ArrayList | CopyOnWriteArrayList |
HashSet | CopyOnWriteArraySet |
HashMap | ConcurrentHashMap |
-
4组API:队列一般可以检测第一个元素是谁!
方法 | 第一组会抛出异常 | 返回一个布尔值,不会抛出异常 | 延时等待 | 一直等待(阻塞) |
插入 | add() | offer(e) | offer(e,time) | put() |
取出 | remove() | poll() | poll(time) | take() |
检查 | element() | peek() | - | - |
非阻塞队列7个
阻塞队列6个
since 1.8
CompletableFuture 异步回调
函数式编程(java.util.function)
lambda表达式:()->{}
4个基本的函数式接口:
-
Function : 有一个输入参数有一个输出参数
-
Consumer:有一个输入参数,没有输出参数
-
Supplier:没有输入参数,只有输出参数
-
Predicate:有一个输入参数,判断是否正确
Stream(java.util.stream)
-
流:从支持数据处理操作的源生成的一系列元素。
-
2类流操作:中间操作、终端操作。
-
流利用内部迭代:迭代通过filter、map、sorted等操作被抽象掉了,filter、map等中间操作会返回一个流,并可以链接在一起,可以用来设置一条流水线,但不会生成结果。
-
forEach和count等终端操作会返回一个非流的值,并处理流水线以返回结果。
-
流中的元素是按需计算的。
阅读《Java 8 in Action》、《Java 8函数式编程》
单例模式实现对比
实现方式 | 可延迟加载 | 多线程环境安全 | |
饿汉式 | N | Y | Y |
懒汉式 | Y | N | Y |
懒汉式+synchronized | Y | Y 但性能低下 | Y |
DCL懒汉式 | Y | Y 但可能产生NPE | Y |
DCL懒汉式+volatile | Y | Y | Y |
Holder方式 | Y | Y | Y |
枚举类式 | N | Y | N |
枚举类+Holder方式 | Y | Y | Y |
原子引用
java.util.concurrent.atomic包
ABA问题