1. Java 并发包(*JUC)
2. 到底什么是锁
3. 并发原子类*
4. 并发工具类详解*
5. 第 7 课总结回顾与作业实践
一 Java并发包
JDK核心库的包有哪些?
java.lang.* 最基础,Integer/String
java.io.* IO读写,文件操作
java.util.* 工具类,集合/日期
java.math.*
java.net.*
java.rmi.* Java内置的远程调用
java.sql.*
javax.* Java拓展API
sun.* sun的JDK拓展
JUC包内容分类
二 锁是什么
为什么需要显式的Lock / synchronized方式加锁有什么问题?
1、同步块的阻塞无法中断(不能 Interruptibly)
2、同步块的阻塞无法控制超时(无法自动解锁)
3、同步块无法异步处理锁(即不能立即知道是否可以拿到锁)
4、同步块无法根据条件灵活的加锁解锁(即只能跟同步块范围一致)
Lock锁
- 使用方式灵活可控
- 性能开销小
- 锁工具包: java.util.concurrent.locks
Lock 接口设计:
// 1.支持中断的 API
void lockInterruptibly() throws InterruptedException;
// 2.支持超时的 API
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 3.支持非阻塞获取锁的 API
boolean tryLock();
基础接口:
//获取锁; 类比 synchronized (lock)
void lock();
//获取锁; 允许打断;
void lockInterruptibly()
throws InterruptedException;
//尝试获取锁; 成功则返回 true; 超时则退出
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException;
//尝试【无等待】获取锁; 成功则返回 true
boolean tryLock();
//解锁;要求当前线程已获得锁; 类比同步块结束
void unlock();
//新增一个绑定到当前Lock的条件;
Condition newCondition();
示例: (类比: Object monitor)
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
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;
}
}
读写锁
注意:ReadWriteLock 管理一组锁,一个读锁,一个写锁。
读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
所有读写锁的实现必须确保写操作对读操作的内存影响。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。ReadWriteLock 适用于读多写少的并发情况
// 构造方法
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(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
通过 Lock.newCondition()创建。
可以看做是 Lock 对象上的信号。类似于 wait/notify
三个用锁的最佳实践
- 永远只在更新对象的成员变量时加锁
- 永远只在访问可变的成员变量时加锁
- 永远不在调用其他对象的方法时加锁
最小使用锁:
- 降低锁范围
- 细分锁粒度
三 并发原子类
Atomic工具类
无锁技术底层实现原理:
- Unsafe API -> Compare-And-Swap
- CAS指令
CAS 本质上没有使用锁。
并发压力跟锁性能的关系:
1、压力非常小,性能本身要求就不高;
2、压力一般的情况下,无锁更快,大部分都一次写入;
3、压力非常大时,自旋导致重试过多,资源消耗很大
LongAdder对AtomicLong的改进
分段思想,类似于归并思想
四 并发工具类详解
工具类底层原理 AQS(AbstractQueuedSynchronizer,抽象队列同步器)
Semaphore - 信号量
- 准入数量 N
- N =1 则等价于独占锁
public class SemaphoreCounter {
private int sum = 0;
private Semaphore readSemaphore = new Semaphore(100, true);
private Semaphore writeSemaphore = new Semaphore(1);
public int incrAndGet() {
try {
writeSemaphore.acquireUninterruptibly();
return ++sum;
} finally {
writeSemaphore.release();
}
}
public int getSum() {
try {
readSemaphore.acquireUninterruptibly();
return sum;
} finally {
readSemaphore.release();
}
}
}
CountdownLatch
public static class CountDownLatchTask implements Runnable {
private CountDownLatch latch;
public CountDownLatchTask(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
Integer millis = new Random().nextInt(10000);
try {
TimeUnit.MILLISECONDS.sleep(millis);
this.latch.countDown();
System.out.println(“我的任务OK了:"+Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 使用示例
public static void main(String[] args) throws Exception {
int num = 100;
CountDownLatch latch = new CountDownLatch(num);
List<CompletableFuture> list = new ArrayList<>(num);
for (int i = 0; i < num; i++) {
CompletableFuture<Void> future = CompletableFuture.runAsync(new CountDownLatchTask(latch));
list.add(future);
}
latch.await();
for (CompletableFuture future : list) {
future.get();
}
}
CyclicBarrier
场景: 任务执行到一定阶段, 等待其他任务对齐
Future/FutureTask/CompletableFuture
总结
Java 并发包
什么是锁
并发原子类
并发工具类