资料汇总
书籍:《Java并发编程之美》《Java多线程编程核心技术》《码出高效(第7章)》
同步和异步
同步:A去公司找B,B不在,A在B的座位上等B回来。
异步:A去公司找B,B不在,A对C说,如果B什么时候回来了通知A。
并发和并行
并发:一个饮水机,大家轮流交替接水喝。 强调 轮流
并行:两个饮水机,两队人同时接水喝。 强调 同时发生。
线程上下文切换
cpu分配给线程A 5s的时间来执行任务,但是任务A需要10s才可以执行完毕,当时间片用完的时候需要保存信息,当A再一次分配到时间片的时候可以继续执行后续的任务,这个过程就是上下文切换。
共享资源
多个线程共同操作的资源,可以是一个字段、集合、对象等。
线程安全
简单来说,如果多个线程进行一组操作后可以表现出预期的效果就可以称为线程安全。
为什么要使用多线程?
为了充分榨取cpu的性能,提高程序的处理能力。
JMM
java memory model 翻译为 java 内存模型
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第12章 Java内存模型与线程
jsr-133
JSR-133: JavaTM Memory Model and Thread Specification
volatile
JMM的原子性、可见性与有序性
原子性:
定义:
可见性:
定义:一个线程的修改,其他线程是否感知。
实现:volatile,synchronized,final
有序性:
定义:
详细内容查看上面的连接。
指令重排序
cpu会优化指令的顺序以达到最佳的状态。
单线程:虽然指令的顺序会变化,cpu会保证执行结果的正确。
那么为什么多线程的指令重排序会有问题?
下面我们用一个示例来说明
public class Resort01 {
private static int x = 0;
private static int y = 0;
private static int a = 0;
private static int b = 0;
public static void main(String[] args) throws Exception {
int i = 0;
while (true) {
i++;
x = 0;
y = 0;
a = 0;
b = 0;
Thread t1 = new Thread(() -> {
a = 1;
x = b;
});
Thread t2 = new Thread(() -> {
b = 1;
y = a;
});
t1.start();
t2.start();
t1.join();
t2.join();
if (x == 0 && y == 0) {
System.out.println("i=" + i);
System.out.println("x=" + x);
System.out.println("y=" + y);
System.out.println("a=" + a);
System.out.println("b=" + b);
break;
}
}
}
}
-----
i=745
x=0
y=0
a=1
b=1
顺序执行:t1先执行t2后执行,结果为 x=0 y=1 反之 x=1 y=0。
交替执行:如果交替执行也就是 a=1 b=1 后 x=1 y=1。
wtf,站在顺序阅读的角度,a或b的赋值,竟然对于x或y是不可见的。我们可以根据先行发生也就是可见性原则来快速的判断是否可见。
修改 if 条件
if (x == 1 && y == 1) {
发现竟然没有成立的情况,假设以重排序的思路来理解,既然同时为0(交替重排序)可以成立且随机,成立时为x=b y=a a=1 b=1,那么同时为1(交替且不排序)也应该成立才对,a=1 b=1 y=a x=b 这种顺序也应该存在,这里只能用每次都重排序来解释。
使用 volatile 关键字修饰发现 x == 0 && y == 0 条件不再成立,推测由于禁止了指令重排序至少有一个是1,所以不会出现同时为0的情况。
private static int x = 0;
private static int y = 0;
private volatile static int a = 0;
private volatile static int b = 0;
这个时候修改判断条件 if (x == 1 && y == 1)
i 出现的次数为2774,6907,9981…,多次运行结果随机。 说明由于代码较少,小概率交替执行,大概率轮流执行。
锁的常见概念
可重入
是否可以获取同一把锁,或者是重复进入一段加锁的代码块。
可中断
是否可以响应中断信号,从阻塞的状态退出。
synchronized 不响应中断一直阻塞。而 ReentrantLock 可以响应中断退出阻塞状态。
公平锁
是否可以按照队列的形式来依次获取锁,synchronized 是抢占式,而 ReentrantLock 可以开启公平锁,但是公平锁会影响性能一般不会采用。
自旋锁
由于用户态向内核态转化的过程需要耗费一定的资源。
线程获取锁失败时不会立即由用户态切换到内核态挂起,会空转一会尝试获取锁。
使用时需要考虑空转的性能消耗和状态转化的消耗比较问题。
java -XX:+PrintFlagsFinal -version | findstr Spin
PreInflateSpin
SafepointSpinBeforeYield
独占锁与共享锁
独占锁(排他锁):同一时间只能被一个线程持有。
共享锁:同一时间可以被多个线程持有。
ReentrantReadWriteLock的读和写
Thread类
juc
ReentrantLock
Condition
LockSupport
线程通信的三种方式,wait notify,Condition 的 await 和 signal ,LockSupport
两种方法的问题,都需要一个 锁 的获取过程,都需要 唤醒 发生在 等待之后 ,否则无法唤醒,除非设置了最大等待时间等。
park 方法 阻塞当前线程。
unpark 唤醒指定线程 。
park 方法是可以响应中断的,要结合源 native源码 学习 ,待完善。
总结
只有 Condition 可以实现等待过程不响应中断信号。
只有LockSupport 不需要获取到锁。
只有Object的wait不支持等待到某个日期返回。
只有LockSupport 可以实现先唤醒后等待仍然可以唤醒。
Semaphore
应用场景: 多少个线程访问特定资源,控制最大并发进行限流。
假设厕所有5个位置,发现标识没人,于是进去锁门,当5个人都进去后,第6个人进来只能等待,等其中一个人开门后才能上厕所,简单说就是控制最大可以有多少个线程来操作共享资源。
fair 为 true 代表 排队依次获取许可,false 代表竞争获取锁,可以理解为各凭本事插队。
如何正确使用信号量
public class Semaphore01 {
static Semaphore semaphore = new Semaphore(5);
public static void main(String[] args) {
System.out.println(semaphore.availablePermits());
for (int i = 0; i < 10; i++) {
boolean acquire = false;
try {
acquire = semaphore.tryAcquire(200, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (acquire) {
semaphore.release();
}
}
}
System.out.println(semaphore.availablePermits());
}
}
注意问题,释放许可要保证获取过,否则许可会凭空增加。 这种增加许可的操作可以用作死锁恢复的场景。
如果将信号量放在业务代码的入口可以实现最大并发的限流操作,不过不够灵活。
CountDownLatch
应用场景: 主管等待3个小弟到齐开会,来一个人计数器-1,到0的时候等待结束,开会。
主要 四个方法 构造方法 创建指定数量 闭锁 。无限期等待或者超时等待均响应中断。 闭锁计数器 -1 。
使用场景:
主线程等待子线程完成任务,可以无限制等待、有期限等待和响应中断三种。
CountDownLatch countDownLatch = new CountDownLatch(2);
countDownLatch.countDown();
countDownLatch.await();
CyclicBarrier
应用场景:一张N个座位的餐桌,等待人到齐后开始吃饭。
一个 循环障碍 可以 重复使用 。
//创建
static CyclicBarrier barrier= new CyclicBarrier(5);
//等待
barrier.await();
Runnable 构造方法
这个 Runnable 方法的执行者 是 最后一个 线程。
破坏屏障并唤醒其他线程。
如果 当前线程 收到 中断信号 则会 打破屏障 。
线程池
Unsafe
原子类
ThreadLocal
阻塞队列
Future
利用Future接口优化
学了这么多,我们如何利用多线程来优化接口的请求速度呢?
当一个接口中存在多个内容,并且相互之前不影响,这个时候就可以使用 Future 来异步执行,同步返回结果。
public Map<String, Object> goodsDetail(long goodsId) throws ExecutionException, InterruptedException {
Map<String, Object> result = new HashMap<>();
Future<String> future = executorService.submit(() -> getGoodsBasic(goodsId));
Future<List<String>> futureImages = executorService.submit(() -> getGoodsImgs(goodsId));
Future<String> futurnDetails = executorService.submit(() -> getGoodsDetails(goodsId));
result.put("getGoodsBasic", future.get());
result.put("getGoodsImgs", futureImages.get());
result.put("getGoodsDetails", futurnDetails.get());
return result;
}
CompletableFuture
深入浅出java.util.concurrent.CompletableFuture
AQS
深入浅出java并发编程(AbstractQueuedSynchronizer)(AQS)