JMM
java内存模型,是一种概念。
关于JMM的一些同步约定:
- 线程解锁前,必须把共享内存立刻刷回主存
- 线程加锁前,必须读取内存中最新的值到工作内存中
- 加锁和解锁是同一把锁
内存交互的8个操作
- lock(加锁):作用于主内存的变量,将一个变量标识为线程独占状态
- unlock(解锁):作用于主内存的变量,将处于锁定状态的变量释放出来
- read(读取):作用于主内存的变量,将变量的值从主内存传到线程的工作内存中
- load(载入):作用于工作内存的变量,将read操作读取的值放入工作内存中
- use(使用):作用于工作内存的变量,将工作内存中的变量传输给执行引擎,当虚拟机遇到一个需要使用到变量的值,使用这个指令
- assign(赋值):作用于工作内存的变量,将一个从执行引擎中接收到的值放入工作内存的变量副本中
- store(存储):作用于工作内存的变量,将工作内存变量传递到主内存中
- write(写入):作用于主内存的变量,把Store操作从工作内存得到的变量值放入主内存变量中
必须成对使用
图片里store write反了
Volatile
java虚拟机提供的轻量级同步控制
- 保证可见性
- 不保证原子性
- 禁止指令重排
可见性
public class demo1 {
//不加volatile程序会死循环
//加volatile可以保证可见性
private volatile static int num = 0;
public static void main(String args[]){
new Thread(()->{
while (num ==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
原子性
不可分割
线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败
public class demo2 {
//volatile不保证原子性
private volatile static int num = 0;
public static void add(){
num++;//非原子性操作
}
public static void main(String[] args){
for (int i=1;i<=20;i++){
new Thread(()->{
for (int j=0;j<1000;j++){
add();
}
}).start();
}
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
如果不加lock和synchronized怎样保证原子性
使用原子类
Atomic
//原子类的Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
num.getAndIncrement();
}
指令重排
处理器在进行指令重排的时候,会考虑数据间的依赖性。
内存屏障,CPU指令
- 保证特定的操作执行顺序
- 保证某些变量的内存可见性
单例模式!!
饿汉式
//饿汉式单例
public class Hungry {
//可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式
//懒汉式单例
public class Lazy {
private Lazy(){
}
private volatile static Lazy Lazy = null;
public static Lazy getInstance(){
if (Lazy == null){
Lazy = new Lazy();
}
retrun Lazy;
}
}
多线程下会破坏单例模式
双重检测锁模式 DCL懒汉式
//懒汉式单例
public class Lazy {
private Lazy(){
}
private volatile static Lazy Lazy;
//双重检测锁模式 DCL懒汉式
public static Lazy getInstance(){
if (Lazy == null){
synchronized (Lazy.class){
if (Lazy == null){
Lazy = new Lazy();//不是原子性操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.将对象指向这个空间
*
* 123
* 132 A
* B来了会判断Lazy不等于null,此时lazy还未完成构造
*/
}
}
}
return Lazy;
}
}
有可能通过反射类破坏
//懒汉式单例
public class Lazy {
//信号量
private static boolean tt = false;
private Lazy(){
synchronized (Lazy.class){
if (tt==true){
tt = true;
}else {
throw new RuntimeException("不要使用反射破坏");
}
}
private volatile static Lazy Lazy;
//双重检测锁模式 DCL懒汉式
public static Lazy getInstance(){
if (Lazy == null){
synchronized (Lazy.class){
if (Lazy == null){
Lazy = new Lazy();//不是原子性操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.将对象指向这个空间
*
* 123
* 132 A
* B来了会判断Lazy不等于null,此时lazy还未完成构造
*/
}
}
}
return Lazy;
}
}
CAS
compare and set 比较并交换
比较当前工作内存找那个的值和主内存中的值,如果这个值是期望的,那么执行操作,否则一直循环。
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
unsafe类
自旋锁
ABA问题
线程1,将变量X值改为b,又改回a,
线程2,期望变量X值为a,看到变量X值为a则进行下一步操作,但是不知道变量X值已经被改过了。
解决:
原子引用:
版本号
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
atomicStampedReference.compareAndSet(1,2,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
锁
公平锁:不允许插队,严格按照先来后到;
非公平锁:允许插队,默认非公平。
//非公平锁
Lock lock = new ReentrantLock();
//公平锁
Lock lock = new ReentrantLock(true);
可重入锁(递归锁)
拿到了外面的锁,也可以拿到里面的锁
自旋锁
死锁
产生的条件:
- 互斥条件:进程一旦获取到某个资源就会一直占有直到结束线程,其他线程想要获取此资源只能等待。
- 不可剥夺条件:进程所获取的资源,必须自己释放,其他线程无法抢夺。
- 请求和保持条件:进程已经持有一个资源,又提出了一个新资源的申请,但是新资源被其他进程占有,此时请求进程被阻塞,对自己已获得的资源保持不放。
- 循环等待条件:存在进程资源的循环等待链。
死锁检测:
jps-1定位进程号
jstack-进程号 找到死锁问题
排查:
日志
堆栈