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接口设计:

  1. 支持中断的API:获取锁,允许被打断

void lockInterruptibly() throws InterruptedException;

  1. 支持超时的API:尝试在指定时间内获取锁,成功则返回true,超时则退出。

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

  1. 支持非阻塞获取锁的API:尝试不等待获取锁,成功则返回true。

boolean tryLock();

  1. 获取锁:类比synchronized(lock)

void lock();

  1. 解锁:前提要求当前线程已获得锁,类比同步块结束。

void unlock();

  1. 新增一个绑定到当前Lock条件

Condition newCondition();

  • 可重入锁ReentrantLock();持有当前锁的线程第二次想去拿到这个锁时,可以直接拿到。锁不会阻塞当前持有它自己的线程。
  • 公平锁:锁的参数为true,当前线程执行完之后,将锁给下一个排在第一位的线程,FIFO。
  • 非公平锁:锁的参数为false,当前线程执行完之后,将锁随机分配给等待队列的任一线程。

读写锁ReadWriteLock的接口实现

  • 获取读锁:共享锁

Lock readLock();

  • 获取写锁:独占锁,串行锁

Lock writeLock();
注意:
ReadWriteLock管理一组锁,一个读锁,一个写锁。读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。所有读写锁的实现必须确保写操作对读操作的内存影响。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。ReadWriteLock适用于读多写少的并发情况。

  1. public class ReadWriteLockCounter{
  2. private int sum = 0;
  3. private ReadWriteLock lock = new ReentranReadWriteLock(true); // 可重入锁,读写锁,公平锁
  4. public int incrAndGet() {
  5. try {
  6. lock.writeLock().lock(); // 写锁,独占锁,被读锁排斥
  7. return ++sum;
  8. } finally {
  9. lock.writeLock.unlock();
  10. }
  11. }
  12. public int getSum() {
  13. try {
  14. lock.readLock().lock(); // 读锁,共享锁,保证可见性
  15. return ++sum;
  16. } finally {
  17. lock.writeLock.unlock();
  18. }
  19. }
  20. }

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);

锁的最佳实践

  1. 永远只在更新对象的成员变量时加锁
  2. 永远只在访问可变的成员变量时加锁
  3. 永远不在调用其他对象的方法时加锁
  4. 降低锁范围
  5. 细分锁粒度

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.

  1. ArrayList等链表加上synchronized —> Vector实现
  2. Collections.synchronizedList(List list)静态方法:强制将入参list的操作都加上同步
  3. Array.asList:本质是固定长度的array[]数组,不允许add增remove删,但可以set替换元素。
  4. 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.服务器端控制

分布工下的锁和计数器

加锁需要考虑的问题

  1. 粒度
  2. 性能
  3. 重入
  4. 公平/不公平
  5. 自旋锁(spinlock):CAS乐观锁常用。
  6. 场景: 脱离业务场景谈性能都是耍流氓

线程间协作与通信

1.线程间共享

  • static(堆内存)
  • Lock
  • Synchronized

2.线程间协作/聚合

  • Thread#join()
  • Object#wait/notify/notifyAll
  • Future/Callable
  • CountdownLatch
  • CyclicBarrier