JMM
屏蔽硬件和操作系统的内存访问差异,JMM定义JVM在计算机内存中的工作方式;
JMM决定一个线程对共享变量的写入何时对另一个线程可见
线程间通信
-
共享内存
-
消息传递 wait(),notify()
线程间同步
共享内存并发模型中,同步是显示的
主内存和工作内存
所有的变量都储存在主内存中,每个线程有自己的工作内存;工作内存存储在高速缓存或寄存器中,保存该线程使用变量的主内存副本拷贝
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oYZm9hvX-1630569171437)(pic\image-20210831160001077.png)]
内存间交互操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D40QkUfh-1630569171441)(pic\image-20210831160047897.png)]
- read:把一个变量的值从主内存传输到工作内存中
- load:在 read 之后执行,把 read 得到的值放入工作内存的变量副本中
- use:把工作内存中一个变量的值传递给执行引擎
- assign:把一个从执行引擎接收到的值赋给工作内存的变量
- store:把工作内存的一个变量的值传送到主内存中
- write:在 store 之后执行,把 store 得到的值放入主内存的变量中
- lock:作用于主内存的变量
- unlock
八种指令规则
- 只能配对出现
当指令不成对出现从就会造成程序异常 通过volati解决
JMM特性
原子性
JMM保证8个操作有原子性;但是JMM允许虚拟机将没有被volatile修饰的64位数据(long,double)读写划分两次,所以load,store,read,write可以不具备;
只是单个操作具有原子性可以使用Atomic;或者使用synchronized,代表lock和unlock
可见性
线程修改了共享变量的值,其他线程可以马上得知修改;JMM通过在变量修改后将新值同步回主内存,在读取前从主内存刷新变量
实现可见三种方式
- volatile
- synchronized,变量执行unlock前,把变量值同步回主内存
- fianal,被final修饰,在构造器构造完成且没有this逃逸,其他线程通过this引用访问到一半的对象
有序性
指令重排,JMM中允许编译器和处理器对指令重排序
volatile通过添加内存屏障,重排序时不能把后面的指令放到内存屏障前
synchronized,保证每个时刻只有一个线程执行同步代码,让线程顺序执行
内存屏障
LoadLoad,StoreStore,LoadStore,StoreLoad
- LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
- StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
- LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
- StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。*它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能*
volatile
写操作前插入StoreStore,写操作后StoreLoad
读前LoadLoad,读后LoadStore
final
- 一个对象所有的final域被写入完毕才能引用和读取
- 写final域完毕,构造结束前,插入SS,保证前面的final写入对其他线程cpu可见,阻止重排序,读final域前插入LL
先行发生原则
上面提到了可以用 volatile 和 synchronized 来保证有序性。除此之外,JVM 还规定了先行发生原则,让一个操作无需控制就能先于另一个操作完成。
1. 单一线程原则
Single Thread rule
在一个线程内,在程序前面的操作先行发生于后面的操作。
![](https://i-blog.csdnimg.cn/blog_migrate/c4cd8d897d80b07bbca1495fa29145bf.png)
2. 管程锁定规则
Monitor Lock Rule
一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。
![](https://i-blog.csdnimg.cn/blog_migrate/d4ba8b57a3b3ccb24f3d31f6dca4d902.png)
3. volatile 变量规则
Volatile Variable Rule
对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。
![](https://i-blog.csdnimg.cn/blog_migrate/7c0e4b69092d97a8dad2f1afe90ed2d9.png)
4. 线程启动规则
Thread Start Rule
Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。
![](https://i-blog.csdnimg.cn/blog_migrate/91589f6fd82436754ceb104b527a8def.png)
5. 线程加入规则
Thread Join Rule
Thread 对象的结束先行发生于 join() 方法返回。
![](https://i-blog.csdnimg.cn/blog_migrate/38c1cdee12cc1b900ce1d62f7f5c5c15.png)
6. 线程中断规则
Thread Interruption Rule
对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。
7. 对象终结规则
Finalizer Rule
一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
8. 传递性
Transitivity
如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。
线程安全
不可变
- final
- 枚举类
Collections.unmodifiableXXX() 获得不可变的集合
互斥同步
排他锁
非阻塞同步
互斥会带来阻塞同步;乐观并发策略,先操作,失败则补偿(自旋)
CAS
CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。
原子类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JM0qdm5y-1630569171443)(pic\image-20210831163831956.png)]
ABA
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NKdC6zoz-1630569171446)(pic\image-20210831163848012.png)]
带版本号的原子类AtomicStampedReference
无同步方案
栈封闭
多个线程访问同一个方法的局部变量
public static void add(){
int a = 0;
a++;
System.out.println(a);
}
public static void main(String[] args) throws ExecutionException, InterruptedException, BrokenBarrierException {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
executorService.execute(Demo::add);
}
executorService.shutdown();
}
ThreadLocal
线程本地存储;
为了理解 ThreadLocal,先看以下代码:
public class ThreadLocalExample1 {
public static void main(String[] args) {
ThreadLocal threadLocal1 = new ThreadLocal();
ThreadLocal threadLocal2 = new ThreadLocal();
Thread thread1 = new Thread(() -> {
threadLocal1.set(1);
threadLocal2.set(1);
});
Thread thread2 = new Thread(() -> {
threadLocal1.set(2);
threadLocal2.set(2);
});
thread1.start();
thread2.start();
}
}
它所对应的底层结构图为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-biXOQAsA-1630569171448)(pic\image-20210831174952164.png)]
当一个线程有多个 ThreadLocal 时,需要一个容器来管 理多个 ThreadLocal,ThreadLocalMap 的作用就是这个,管理线程中多个 ThreadLocal
当调用一个 ThreadLocal 的 set(T value) 方法时,先得到当前线程的 ThreadLocalMap 对象,然后将 ThreadLocal->value 键值对插入到该 Map 中。
内存泄露
已经动态分配的堆内存由于某种原因没有释放或者无法释放,造成系统内部的浪费;内存泄露的堆积将导致内存溢出
key=threadLocal强引用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mmUF9f6I-1630569171451)(pic\image-20210831191013753.png)]
弱引用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j54szvGB-1630569171452)(pic\image-20210831191448567.png)]
原因
- 没有手动清楚entry
- currentThread依然运行
解决
- 使用完ThreadLocal调用remove方法删除对应entry
- 使用完ThreadLocal当前Thread结束运行
线程池
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oRGF5llX-1630569171454)(pic\image-20210831164731476.png)]
类结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hhC7u1TE-1630569171456)(pic\image-20210831165235851.png)]
ExecutorService丰富了对任务的执行和管理
// 关闭,不会接受新的任务,也不会等待未完成的任务
// 如果需要等待未完成的任务,可以使用 awaitTermination 方法
void shutdown();
// executor 是否已经关闭了,返回值 true 表示已关闭
boolean isShutdown();
// 所有的任务是否都已经终止,是的话,返回 true
boolean isTerminated();
// 在超时时间内,等待剩余的任务终止
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// 提交有返回值的任务,使用 get 方法可以阻塞等待任务的执行结果返回
<T> Future<T> submit(Callable<T> task);
// 提交没有返回值的任务,如果使用 get 方法的话,任务执行完之后得到的是 null 值
Future<?> submit(Runnable task);
// 给定任务集合,返回已经执行完成的 Future 集合,每个返回的 Future 都是 isDone = true 的
状态
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 给定任务中有一个执行成功就返回,如果抛异常,其余未完成的任务将被取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
1. AbstractExecutorService 是一个抽象类,封装了 Executor 的很多通用功能,比如:
// 把 Runnable 转化成 RunnableFuture
// RunnableFuture 是一个接口,实现了 Runnable 和 Future
// FutureTask 是 RunnableFuture 的实现类,主要是对任务进行各种管理
// Runnable + Future => RunnableFuture => FutureTask
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
// 提交无返回值的任务
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// ftask 其实是 FutureTask
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
// 提交有返回值的任务
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// ftask 其实是 FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OAkdcQnm-1630569171457)(pic\image-20210831170343344.png)]
private static final int RUNNING = -1 << COUNT_BITS; //接受新任务或者处理队列里的任务。
private static final int SHUTDOWN = 0 << COUNT_BITS; //不接受新任务,但是处理队列中的任务
private static final int STOP = 1 << COUNT_BITS; //不接受,不处理队列中的任务,中断正在执行的任务
private static final int TIDYING = 2 << COUNT_BITS; //TIDYING所有任务被中断,整理状态
private static final int TERMINATED = 3 << COUNT_BITS; //已经完成的时候
常见线程池
-
CachedThreadPool:可缓存的线程池,没有核心线程,非核心线程数量无限大,有需要时创建线程,没有需要时回收线程
-
SecudleThreadPool:周期性执行任务,按计划执行线程任务,有核心线程和非核心线程(无限大)
-
SingleThreadPool:只有一条线程执行任务,适用于顺序任务的应用场景
-
FixedThreadPool:定长的线程池,核心线程即最大线程数量,没有非核心线程
经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响大;提前创建多个线程,放入线程池。
public ThreadPoolExecutor(int corePoolSize//核心线程大小,
int maximumPoolSize,//最大线程数
long keepAliveTime,//超时自动释放
TimeUnit unit,//超时单位
BlockingQueue<Runnable> workQueue//阻塞队列) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory()//线程工厂
, defaultHandler//拒绝策略);
}
handler拒绝策略
- AbortPolicy,不执行新任务,直接抛出异常,提示线程已满
- DisCardPolicy,不执行新任务,丢掉任务,不抛出异常
- DisCardOldSetPolicy,将最早的线程竞争(尝试),成功则替换为新进来的任务执行
- CallerRunsPolicy,直接调用execute来执行当前任务,哪个线程调用哪个,调用main线程执行
线程池定义方法
coreSize==maxSize
很好的应对波峰低谷;需要先设置前提
- allowCoreThreadTimeOut,为false线程空闲时不会回收核心线程
- keepAliveTime 和 TimeUnit 我们都会设置很大,这样线程空闲的时间就很长,线程就不会 轻易的被回收。
maxSize无界+SynchronousQueue
任务被消费时,才会返回,请求知道当前请求已经在被消费;
消耗资源,大量请求到来需要新建大量的线程处理
maxSize 有界 + Queue 无界
把请求放到队列中等待,减少线程间cpu竞争
请求实时性不能保证
licy,直接调用execute来执行当前任务,哪个线程调用哪个,调用main线程执行
线程池定义方法
coreSize==maxSize
很好的应对波峰低谷;需要先设置前提
- allowCoreThreadTimeOut,为false线程空闲时不会回收核心线程
- keepAliveTime 和 TimeUnit 我们都会设置很大,这样线程空闲的时间就很长,线程就不会 轻易的被回收。
maxSize无界+SynchronousQueue
任务被消费时,才会返回,请求知道当前请求已经在被消费;
消耗资源,大量请求到来需要新建大量的线程处理
maxSize 有界 + Queue 无界
把请求放到队列中等待,减少线程间cpu竞争
请求实时性不能保证