JUC

JUC:并发工具包java.util.concurrent

基本使用

lock与synchronized区别

  • lock是工具包,synchronized是java关键字
  • lock需要手动释放锁,synchronized由jvm管理
  • lock支持悲观锁/乐观锁,取消/退出机制,synchronized是非公平锁,不支持取消/退出机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Lock 锁接口
ReentrantLock 可重入锁,默认悲观锁
构造:
new ReentrantLock(true) 创建锁对象时,设置为乐观锁
方法:
tryLock() 尝试获取锁,获取成功返回true,获取失败返回false,避免阻塞过久
tryLock(long,TimeUnit) 在设定时间内尝试获取锁,获取成功返回true,获取失败返回false
ReadWriteLock 读写锁接口
ReentrantReadWriteLock 读写锁,支持锁降级
构造:
new ReentrantReadWriteLock()
方法:
readLock().lock(); 读锁
writeLock().lock(); 写锁

TimeUnit 封装后的Thread.sleep支持各种单位,是一个枚举类,内置各种单位对象,之后可以调用sleep

线程通信

1
2
3
4
5
6
7
Condition 用来控制线程交互
构造:
lock.newCondition() 创建锁对应的Condition,可以构造多个Condition实现精准操作
方法:
Condition.await() 用来阻塞线程,使线程睡眠
Condition.signal() 唤醒当前对象的await()阻塞线程
Condition.signalAll() 用来唤醒所有线程

集合类

  • CopyOnWriteArrayList 线程安全的ArrayList,读写分离,写时复制(写操作为:获取数组,复制数组长度加一,写入数据,更换原数组),适用于读多写少的场景
  • CopyOnWriteHashSet 线程安全的HashSet
  • ConcurrentHashMap 线程安全的HashMap,采用分段锁,写操作只锁局部的节点

辅助类

1
2
3
4
5
6
CountDownLatch(减少计数):经过一定数量的计数释放await
构造:
new CountDownLatch(10) 计数阈值
方法:
countDown() 每次经过进行计数
await() 线程等待,待减法计数完毕等待失效
1
2
3
4
5
CyclicBarrier(循环栅栏)
构造:
new CyclicBarrier(int parties,Runnable runnable)
方法
await() 线程等待,待加法计数完成后等待失效,并启动配置的Runnable线程
1
2
3
4
5
6
Semaphore(信号灯)
构造:
new Semaphore(10) 上限阈值,acquire()时获取一个信号,release()时恢复一个信号
方法:
acquire() 获取信号,信号不足时阻塞线程
release() 释放信号,唤醒其他线程

CAS(Compare and Swap)

  • 由硬件完成的原子操作,不加锁。
  • 具有三个操作数,内存地址,预期的原值,新值。
  • 使用内存地址获取值,比较与原值是否一致,一致则改为新值,否则不做操作,重试。
  • CAS是cpu原语,原语的执行是连续的且不可被中断,在操作系统层面保证了一致。
1
2
3
java.util.concurrent.atomic包中:
AtomicInteger 原子性的Integer,在不加synchronized锁的情况下保证线程安全,使用CAS实现
Atomic... 其他原子性的类型

缺点

  • 只能保证当前对象的原子操作
  • 由于每次只有一个可以成功,并发量大时cpu压力非常大
  • CAS只能保证数据不被更改,但不能发现数据的ABA操作,例如将数据改成另一个值然后再改回来

Unsafe

可以直接操作特定内存的数据,类似c++的指针

Callable接口

与Runnable对比

  • Callable有返回值,接口指定泛型;Runnable没有返回值
  • Callable实现方法call,Runnable实现方法run
  • Callable有抛异常,Runnable没有抛异常

FutureTask适配器

说明:

FutureTask实现了RunnableFutureTask接口,RunnableFuture接口继承了Runnable和Future接口,是Runnable和Callable的适配器

构造:

new FutureTask(Callable callable) 将Callable适配到FutureTask中,再由FutureTask建立线程执行

方法:

get() 获取Callable的返回值,在返回之前阻塞线程,只计算一次,后续将缓存结果

BlockingQueue阻塞队列

说明:

当达到一定条件时阻塞队列,达到一定条件时唤醒队列,比起wait/notify我们只需要定义条件即可。

分类:

1
2
3
4
5
6
7
ArrayBlockingQueue:由数组结构组成的有界阻塞队列
LinkedBlockingQueue:由链表结构组成的有界(默认大小Integer.MAX_VALUE)阻塞队列
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
DelayQueue:使用优先级队列实现的延迟无界阻塞队列
SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列
LinkedTransFerQueue:由链表组成的无界阻塞队列
LinkedBlockingDeque:由链表组成的双向阻塞队列

方法:

1
2
3
4
5
6
7
8
9
10
11
     抛出异常       特殊值      阻塞        超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用
抛出异常:当阻塞队列满时,add插入元素会抛IllegalStateException:Queue full
当阻塞队列空时,remove一处元素会抛NoSuchElementException
特殊值:插入方法,成功true,失败false
移除方法,成功返回出队列的元素,队列没有元素返回null
一直阻塞:当阻塞队列满时,生产者线程继续往队列里put元素,队列回一直阻塞生产者线程知道put数据或想要中断退出。
当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程知道队列可用。
超时退出:当阻塞队列满时,队列会阻塞生产者线程一定时间,超过时间后生产者线程会退出

ThreadPool线程池

核心参数

1
2
3
4
5
6
7
1.核心线程数
2.最大线程数
3.非核心线程空闲超时时间
4.空闲超时时间单位
5.工作队列
6.线程工厂,用于创建线程的工厂
7.拒绝策略

执行流程

有任务时查看是否有线程空闲,没有线程空闲查看核心线程是否达到上限,没有达到上限创建核心线程执行,达到上限时查看工作区是否装满,未装满放到工作区,装满时查看线程数量是否达到上限,没有达到上限时创建非核心线程执行当前任务(工作区满之后触发创建非核心线程的任务),达到上限执行拒绝策略。

JDK内置的拒绝策略

1
2
3
4
AbortPolicy          抛出异常RejectedExecutionException
CallerRunsPolicy 将任务退回给提供者运行
DiscardPolicy 丢弃任务
DiscardOldestPolicy 丢弃工作区中等待最久的任务

API

ThreadPoolExecutor 线程池实现类(推荐使用,自定义参数)
构造:

new ThreadPoolExecutor(核心线程数,最大线程,非核心线程空闲超时时间,空闲超时时间单位,工作队列,线程工厂,拒绝策略)
例:new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,new ArrayBlockingQueue<>(20),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

Executors 线程池工具类(不推荐使用)
构造:

Executors.newFixedThreadPool(4); 固定4个线程的线程池,不写默认1个线程。
Executors.newCachedThreadPool(); 不定长的线程池。

方法:

submit() 有返回值的执行,有各种重载参数
execute() 无返回值的执行,有各种重载参数
shutdown() 销毁线程池

锁降级

在写锁操作中获取读锁,保证能够第一时间读取到写入内容,并保证读取期间不被其他写入。而后进行释放写锁,读锁。

其他

对于线程数量的设置,如果是cpu密集型推荐(cpu核数+1),如果是I/O密集型(cpu核数*(1+平均等待时间/平均执行时间)) 或(cpu核数*2)