JMM基本了解

一、概念

很多人将【java内存结构】与【java内存模型分不清】,【java内存模型】是Java Memory Model (JMM)的意思。
简单说 JMM定义了一套在多线程读写共享数据(成员变量、数组)时,对数据的可见性、有序性、和原子性的规则和保障。

二、原子性

java内存模型中通过synchronized关键字来保证原子性,synchronized重量级,也可以保证可见性
语法:

synchronized(对象){
   要作为原子操作代码
}

用synchronized可以解决并发操作

三、可见性

先看下面一段代码,main线程对run变量的修改对于t线程不可见,导致t线程无法停止

public class ThreadTest {
    static boolean run = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while (true){

            }
        });
        t.start();
        Thread.sleep(1000);
        run = false;//t线程不会预想停下来
    }
}

下面来分析
1.初始状态,t线程刚开始从主内存读取了run的值到工作内存
在这里插入图片描述
2.因为t线程要频繁的从主内存读取run的值,JIT编译器会将run的值缓存至自己工作内存中的高速缓存中(可理解static变成一个局部的变量),减少对主内存run的访问,提高效率
在这里插入图片描述
3.1秒后,main线程修改了run的值,并同步至主存,而t是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
在这里插入图片描述
解决方案:volatile(易变关键字)
它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主内存中获取它的值,线程操作volatile变量都是直接操作主内存

体现了可见性,保证多个线程之间,一个线程对volatile 变量的修改对另一个线程可见,不能保证原子性,仅用在一个写线程,多个读线程的情况

public class ThreadTest {
   volatile static boolean run = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while (run){
            }
        });
        t.start();
        Thread.sleep(1000);
        run = false;//t线程会预想停下来
    }
}

四、有序性

指令重排理解:
同一线程内,JVM会在不影响正确性的前提下,可以调整语句的执行顺序,思考下面一段代码

static int i;
static int j;
//在某个线程内执行如下赋值操作
i =...;//较为耗时的操作
j =...;

可以看到,至于先执行i还是先执行j,对最终结果不产生影响。所以,上面代码真正执行时,既可以是

i =...;//较为耗时的操作
j =...;

也可以是

j =...;
i =...;//较为耗时的操作

这种特性称为指令重排,但多线程下指令重排会影响正确性,例如,著名的double-checked locking模式实现单例

public final class Singleton {
    private static Singleton singleton = null;

    private Singleton() { }

    public static Singleton getSingletonInstance() {
        //实例没有创建,才会进入内部的synchronized代码块
        if (singleton == null) {
            synchronized (Singleton.class) {
                //也许其它线程已经创建了实例,所以需要在判断一次
                if (singleton != null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

以上的实现特点是:

  • 懒惰化
  • 首次使用getInstance()才使用synchronized 加锁,后续使用时无需加锁

以上代码看似完美,但在多线程情况下,没有考虑指令重排的情况,
先调构造方法还是先给静态变量赋值,jvm认为谁先执行谁后执行没有影响
加入两个线程,
线程一先进入同步代码块,先执行singleton = new Singleton();分配空间,对Singleton对象生成了引用地址,此时线程2进入
线程2还未进入同步代码块且singleton 不为空,直接返回对象引用,但是现在问题来啦,t线程还未执行完,如果对象的构造过程比较复杂,那么线程2拿到的是一个不完整的对象实例,因为构造方法还未执行完,有的属性赋值完成,有的属性赋值还未完成,使用时可能会出现问题,当然出现这种几率很小

对instance使用volatile修饰,可以禁用指令重排,但要注意jdk5以上的版本volatile才真正有效

五、happens-before

happens-before规定了哪些写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结

线程对volatile变量的写,对接下来其它线程对该变量的读可见

public class ThreadTest {
    volatile static int x;

    public static void main(String[] args) {
        new Thread(() -> {
            x = 10;
        }, "t1").start();

        new Thread(() -> {
            System.out.println(x);
        }, "t2").start();
    }
}

线程解锁m之前对变量的写,对于接下来对m加锁的其它线程对该变量的读可见

public class ThreadTest {
     static int x;
     static Object m=new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (m){
                x = 10;
            }
        }, "t1").start();

        new Thread(() -> {
            synchronized (m){
                System.out.println(x);
            }
        }, "t2").start();
    }
}

线程start前对变量的写,对该线程开始后对该变量的读可见

public class ThreadTest {
    static int x;

    public static void main(String[] args) {
        x = 10;
        new Thread(() -> System.out.println(x), "t2").start();
    }
}

线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用t1.isAlive()或t1.join()等待它结束)

public class ThreadTest {
    static int x;

    public static void main(String[] args) throws InterruptedException {
        x = 10;
        Thread t1=new Thread(()->{
            x=10;
        },"t1");
        t1.start();
        t1.join();
        System.out.println(x);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值