JMM(java内存模型)

发布时间 2023-06-06 22:30:16作者: coooooookie

一、概念

JMM与java并发编程相关:

1、抽象了线程与主内存的关系,例如线程的共享变量需要放到内存中进行读取

2、规定了java源代码到CPU可执行指令这个转换过程中需要遵守的规范,例如防止指令重排序造成的并发问题

 

二、并发编程的三个特性

1、原子性

一次操作或者多次操作,要么所有的操作全部都得到执行,要么都不执行。

在 Java 中,可以借助synchronized、各种 Lock 实现原子性。

 

2、可见性

当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。

在 Java 中,可以借助synchronizedvolatile 以及各种 Lock 实现可见性。

3、有序性

在 Java 中,volatile 关键字可以禁止指令进行重排序优化

 

三、volatile

1、可以保证变量的可见性:volatile 关键字修饰变量则表明该变量是共享且不稳定的,每次使用它都要到主存中进行读取

2、可以防止指令重排序:将变量声明为 volatile ,在对这个变量进行读写操作的时候,会通过插入特定的 内存屏障 的方式来禁止指令重排序。

双重效验锁代码如下:

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public  static Singleton getUniqueInstance() {
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

  

四、synchronized

1、悲观锁:每次在获取共享资源的时候都会上锁

2、重量级锁:依赖于操作系统的监视器锁来实现,操作系统要想阻塞或者唤醒一个线程都需要从用户态转到内核态,需要较长的时间

3、可修饰方法、静态方法、代码块

4、可保证可见性、原子性

修饰代码块会对括号里指定的对象/类加锁:

  • synchronized(object) 表示进入同步代码库前要获得 给定对象的锁
  • synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁

 

五、乐观锁与悲观锁

1、乐观锁:总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。

 

Compare And Swap(比较与交换):用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。

但是存在ABA问题:

如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然是 A 值,但是不能说明它的值没有被其他线程修改过。因此可以再加上版本号和时间戳进一步验证

 

八、ReentrantLock 

1、实现了Lock接口,是可重入的悲观锁

2、比synchronized强大,具有等待可中断、轮询、可实现公平锁等功能

3、ReentrantLock 的底层就是由 AQS (抽象队列同步器)来实现的。

AQS 使用 int 成员变量 state 表示同步状态,通过内置的 线程等待队列 来完成获取资源线程的排队工作。

 

ReentrantLock 为例,state 初始值为 0,表示未锁定状态。A 线程 lock() 时,会调用 tryAcquire() 独占该锁并将 state+1 。此后,其他线程再 tryAcquire() 时就会失败,直到 A 线程 unlock()state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。