Java并发包JUC
JDK核心库rt.jar里的包
- java.lang.* 最核心的包
- java.io.*
- java.util.*
- java.math.*
- java.net.*
- java.rmi.*
- java.sql.*
- javax.*
-
JUC
java.util.concurrent 并发包,五大模块:
- 子包
- atomic 原子类
- AtomicInteger
- locks 锁
- Lock
- Condition
- ReadWriteLock
- atomic 原子类
- 线程池Executor
- Future
- Callable
- Executor
- 工具类Tools
- CountDownLatch
- CyclicBarrier
- Semaphore
- 集合类Collections
- CopyOnWriteArrayList
- ConcurrentMap
锁
为什么需要显示的Lock
- synchronized 可以加锁,是个隐式的锁
- 方法锁
- 对象锁
- 代码块锁
- wait/nofity也可以看作加锁/解锁
原因
java.util.concurrent.locks
- 使用方式灵活可控
- 根据条件动态调整范围
- 控制锁的时间
- 性能开销小
基础接口
| 重要方法 | 说明 | | —- | —- | | void lock(); | 获取锁;类比sychronized(lock) | | void lockInterruptibly() throws InterruptedException; | 获取锁;允许打断 | | boolean tryLock(); | 尝试获取锁,无等待,成功返回true | | void unlock(); | 解锁;要求当前线程已获得锁;类比同步块结束 | | Condition newCondition(); | 新增一个绑定到当前Lock的条件;
示例:(类比:Object monitor)
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition(); |
可重入锁
- new ReentrantLock()
- 可重入锁即某个线程获取到了锁,在未释放锁之前如果再次执行到锁的代码则可直接进入,不必等待
- 传入true为公平锁
- 先来排队的线程先获取到锁
传入false为非公平锁
排队中的线程谁先获取到下一把锁不确定
public class LockCounter{private int sum = 0;private Lock lock = new ReentrantLock(true);public int addAndGet(){try{lock.lock();return ++sum;}finally{lock.unlock();}}public int getSum(){return sum;}}
读写锁
| 重要方法 | 说明 | | —- | —- | | Lock readLock(); | 获取读锁;共享锁 | | Lock writeLock(); | 获取写锁;独占锁(也排斥读锁) |
// 构造方法public ReentrantReadWriteLock(boolean fair){sync = fair ? new FairSync() : new NonfairSync();readerLock = new ReadLock(this);writerLock = new WriterLock(this);}public class ReadWriteLockCounter{private int sum = 0;// 可重入 - 读写锁 - 公平锁private ReadWriteLock lock = new ReentrantReadWriteLock(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.readLock().unlock();}}}
锁优化
- 锁粒度:减小锁的范围
-
Condition
wait/notify/notifyAll是作用在Object
- await/signal/signalAll是作用在Lock | 重要方法 | 说明 | | —- | —- | | void await() throws InterruptedException | 等待信号;类比Object#wait() | | void awaitUninterruptibly(); | 等待信号; | | boolean await(long time, TimeUnit unit) throws | 等待信号;超时则返回false | | boolean awaitUntil(Date deadline) throws | 等待信号;超时则返回false | | void signal(); | 给一个等待线程发送唤醒信号;类比Object#notify() | | void signalAll(); | 给所有等待线程发送唤醒信号;类比Object#notifyAll(); |
LockSupport
- 锁当前线程
-
用锁最佳实践
永远只在更新对象的成员变量时加锁
- 永远只在访问可变的成员变量时加锁
-
并发原子类
Atomic工具类
原子类工具包java.util.concurrent.atomic

public class AtomicCounter{private AtomicInteger sum = new AtomicInteger(0);public int incrAndGet(){return sum.incrementAndGet();}public int getSum(){return sum.get();}}
无锁技术
底层原理
- UnsafeAPI-Compare-And-Swap
- CPU硬件指令支持:CAS指令
- CAS指令作为乐观锁实现,通过自旋重试保证写入
- Atomic实现
- volatile保证主内存可见
- CAS保证原子性
CAS
- 乐观锁/自旋锁
举例
AtomicInteger和AtomicLong里的value是所有线程竞争读写的热点数据;
- 将单个value拆分成跟线程一样多的数组Cell[]
- 每个线程写自己的Cell[i]++,最后对数组求和
多路归并的思想
在并发比较低时
- 无锁简单,效率高
- 在并发稍高时
- 无锁因为锁的范围最小,效率稍高
在并发非常高时
简单机制
- wait/notify
- Lock/Condition
复杂场景
AbstractQueuedSynchronizer,抽象队列同步器
- 是构建锁或者其他同步组件的基础,是JUC包中的核心基础组件
- Semapheore
- CountDownLatch
- ReentrantLock
- ReentrantReadWriteLock
- 两种资源共享方式,子类负责实现公平OR非公平
- 独占
- 共享
Semaphore
- 信号量
- 准入数量N,N=1则等价于独占锁
-
CountdownLatch
使用场景:Master线程等待Worker线程把任务执行完

-
CyclicBarrier
场景:任务执行到一定阶段,等待其他任务对齐
- 示例:等待所有人都到达,再一起开吃
对比
Future/FutureTask/CompletableFuture
常用线程安全类型
JDK基础数据类型与集合类
- List
- ArrayList
- LinkedList
- Vector
- Stack
- Set
- LinkedSet
- HashSet
- TreeSet
- Queue
- Deque
- LinkedList
- Map
- HashMap
- LinkedHashMap
- TreeMap
Dictionary
基本特点
- 基于数组,便于按index访问
- 超过数组需要扩容,成本比较高
- 原理
- 使用数组模拟列表,默认大小10
- 扩容X1.5
安全问题
基本特点
- 使用链表实现,无需扩容
- 不知道容量,插入多的情况
- 原理
- 使用双向指针将所有节点连起来
安全问题
既然线程安全是写冲突和读写冲突导致的
- 最简单的办法就是读写都加锁
例如
核心改进原理
- 写加锁,保证写不会混乱
- 写在一个copy副本上,而不是原始数据
- 特点
- 读写分离
- 最终一致
HashMap
- 基本特点
- 空间换时间
- 哈希冲突不大的情况下查找数据性能很高
- 用途
- 存放指定key的对象
- 缓冲对象
- 原理
- 使用hash原理,存kv数据
- 初始容量16,扩容X2
- 负载因子0.75
- JDK8以后,在链表长度到8&数组长度到64时,使用红黑树

安全问题
基本特点
- 继承自HashMap,对Entry集合添加了一个双向链表
- 用途
- 保证有序
- 特别是Java8 stream操作的toMap时使用
- 原理
- 同LinkedList
- 包括插入顺序和访问顺序
安全问题
Java7
- 引入了segment实现分段锁,理论上最大并发等于 segment个数
- 默认16个segment(concurrentLevel=16),降低锁粒度
- 类比Segment[]就是分库 HashEntry[]就是分表

Java8
线程本地变量
- 场景:每个线程一个副本
- 不改方法签名静默传参
- 及时进行清理
- 可以看作是Context模式,减少显式传递参数 | 重要方法 | 说明 | | —- | —- | | public ThreadLocal() | 构造方法 | | protected T initialValue() | 覆写-设置初始默认值 | | void set(T value) | 设置本线程对应的值 | | void remove() | 清理本线程对应的值 | | T get() | 获取本线程对应的值 |




