G-1.JUC并发工具包java.util.concurrency
锁 | synchronized/wait |
---|---|
原子类 | sum++多线程安全 |
线程池 | new Thread()管理 |
工具类 | 线程间协作信号量 |
集合类 | 线程安全集合类 |
- 锁机制类 Locks : Lock, Condition, ReentrantLock, ReadWriteLock,LockSupport
- 原子操作类 Atomic : AtomicInteger, AtomicLong, LongAdder
- 线程池相关类 Executor : Future, Callable, Executor, ExecutorService
- 信号量三组工具类 Tools : CountDownLatch, CyclicBarrier, Semaphore
- 并发集合类 Collections : CopyOnWriteArrayList, ConcurrentMap
G-2.锁Lock技术java.util.concurrency.locks
定义:对某一块资源占用的一个标记所有权
synchronized 方式的缺点:
1、同步块的阻塞无法中断(不能Interruptibly)
2、同步块的阻塞无法控制超时(无法自动解锁)
3、同步块无法异步处理锁(即不能立即知道是否可以拿到锁)
4、同步块无法根据条件灵活的加锁解锁(即只能跟同步块范围一致)
总结:无法查看状态,无法加中间变量中断,只能死排着。
Lock接口设计:
- 支持中断的API:获取锁,允许被打断
void lockInterruptibly() throws InterruptedException;
- 支持超时的API:尝试在指定时间内获取锁,成功则返回true,超时则退出。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
- 支持非阻塞获取锁的API:尝试不等待获取锁,成功则返回true。
boolean tryLock();
- 获取锁:类比synchronized(lock)
void lock();
- 解锁:前提要求当前线程已获得锁,类比同步块结束。
void unlock();
- 新增一个绑定到当前Lock条件
Condition newCondition();
- 可重入锁ReentrantLock();持有当前锁的线程第二次想去拿到这个锁时,可以直接拿到。锁不会阻塞当前持有它自己的线程。
- 公平锁:锁的参数为true,当前线程执行完之后,将锁给下一个排在第一位的线程,FIFO。
- 非公平锁:锁的参数为false,当前线程执行完之后,将锁随机分配给等待队列的任一线程。
读写锁ReadWriteLock的接口实现
- 获取读锁:共享锁
Lock readLock();
- 获取写锁:独占锁,串行锁
Lock writeLock();
注意:
ReadWriteLock管理一组锁,一个读锁,一个写锁。读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。所有读写锁的实现必须确保写操作对读操作的内存影响。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。ReadWriteLock适用于读多写少的并发情况。
public class ReadWriteLockCounter{
private int sum = 0;
private ReadWriteLock lock = new ReentranReadWriteLock(true); // 可重入锁,读写锁,公平锁
public int incrAndGet() {
try {
lock.writeLock().lock(); // 写锁,独占锁,被读锁排斥
return ++sum;
} finally {
lock.writeLock.unlock();
}
}
public int getSum() {
try {
lock.readLock().lock(); // 读锁,共享锁,保证可见性
return ++sum;
} finally {
lock.writeLock.unlock();
}
}
}
Condition接口实现
定义:专门作用在Lock显示对象上的wait/notify机制。
- 等待信号:void await() throws InterruptedException;
- 等待信号:void awaitUninterruptibly();
- 等待信号:boolean await(long time, TimeUnit unit) throws InterruptedException;
- 等待信号:boolean awaitUtil(Date deadline) throws InterruptedException;
- 唤醒信号:void singal();
- 唤醒信号:void singalAll();
LockSupport锁当前线程接口实现
定义:类似于Thread类的精态方法,专门处理当前(执行此代码)线程。
- 暂停当前线程,无期限
public static void park(Object blocker);
- 暂停当前线程,有超时限制
public static void park(Object blocker, long nanos);
- 暂停当前线程,直到某个时间点
public static void park(Object blocker, long deadline);
- 恢复当前线程:一个被暂停的线程无法自己唤醒自己,需要用其他线程来唤醒。
public static void unpark(Thread thread);
public static Object getBlocker(Thread thread);
锁的最佳实践
- 永远只在更新对象的成员变量时加锁
- 永远只在访问可变的成员变量时加锁
- 永远不在调用其他对象的方法时加锁
- 降低锁范围
- 细分锁粒度
G-3.无锁技术Atomic原子计数工具类java.util.concurrent.atomic
实现原理
1.无锁技术Atomic底层实现原理
- Unsafe API:CAS(CompareAndSwap)
- CPU硬件指令支持方式:CAS指令
- Value的可见性:volatile关键字
2.底层使用技术
- native修饰:该方法实现不在JDK层面,而在JVM内部。JVM调用native方法最终调用的是CPU硬件CAS指令。
- volatile修饰:该变量是对所有线程可见的,主内存里的数值变更后,各个线程内存副本里的值也同步更新。
有锁与无锁的使用场景:并发压力相关
1、压力非常小,性能本身要求就不高
2、压力一般的情况下,无锁更快,大部分都一次写入
3、压力非常大时,自旋导致重试过多,资源消耗很大。
3.多路归并的分段思想
LongAdder AtomicLong
其他使用场景
- 算法快排
- G1 GC
- ConcurrentHashMap
G-4.并发工具类
基础:
AQS(AbstractQueueSynchronizer):队列同步器,构建锁或者其他同步组件的基础。是java并发工具包JUC里最核心的基础组件。它抽象了我们多个线程组成的线程队列。
参考代码:java0.conc0303.tool/java0.conc0303.future
1.Semaphore信号量
2.CountDownLatch:基于AQS实现
3.CyclicBarrier:基于可重入锁Condition.await/singalAll实现
4.Future/FutureTask/CompletableFuture
G-5.常用线程安全类型
List链表线程安全
既然链表的线程安全是写冲突和读写冲突导致的,最简单的办法就是读写都加锁。以下方法除了xxxList,都有对应的xxxMap,xxxSet.
- ArrayList等链表加上synchronized —> Vector实现
- Collections.synchronizedList(List
list)静态方法:强制将入参list的操作都加上同步 - Array.asList:本质是固定长度的array[]数组,不允许add增remove删,但可以set替换元素。
- Collections.unmodifiableList(List<? extends T> list):将入参list包装成只读,不允许更删改。
CopyOnWriteArrayList:
使用副本机制,写加锁,写在一个Copy副本上,而不是原始数据上,写完后替换掉旧数据的堆栈指针,实现读写隔离。
Map线程安全
ConcurrentHashMap:
JDK7:使用分段锁,默认16个segment,给segment加锁,降低锁粒度。并发级别concurrentLevel=16
JDK8:使用CAS乐观锁,摒弃了分段锁方案,直接使用了一个大的数组。
G-6.线程安全总结和Tips
ThreadLocal
每个线程local本地变量的保存读写的机制,线程隔离。
全局变量线程安全,即使更改,也是线程内部可见。
可以看做是个Context模式,减少显式传递参数。
并行Stream(parallel)-四两拨千斤
使用方法:list.stream().parallel();默认调用线程池的实现,创建一个CPU核数一致的线程池来做多线程并发处理。
伪并发问题(重复提交)
1.客户端页面控制
2.服务器端控制
分布工下的锁和计数器
加锁需要考虑的问题
- 粒度
- 性能
- 重入
- 公平/不公平
- 自旋锁(spinlock):CAS乐观锁常用。
- 场景: 脱离业务场景谈性能都是耍流氓
线程间协作与通信
1.线程间共享
- static(堆内存)
- Lock
- Synchronized
2.线程间协作/聚合
- Thread#join()
- Object#wait/notify/notifyAll
- Future/Callable
- CountdownLatch
- CyclicBarrier