可见性

两个线程之间的数据是不共享的,每个线程都缓存了ready的值,所以第一个线程会一直循环下去,读到的一直都是已失效的ready值


public class NoVisibility {

    private static boolean ready;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (!ready) {

            }
            System.out.println("-------------");
        }).start();
        Thread.sleep(1000L);
        new Thread(() -> {
            System.out.println("start");
            ready = true;
            System.out.println("end");
        }).start();

    }
}
非原子的64位操作

当线程在没有同步的情况下,读到的可能是已经失效的值,但至少这个值是之前的线程设置的,而不是一个随机值,这种安全性也被称为最低安全性

Java内存模型要求,数值的读取和写入都应该是原子操作。但对于非volatile的64位double和long类型的数值,jvm允许将64位的读取和写入操作分解成两个32位的操作。那么对该变量的读取很可能读取到其他线程修改过的不同版本的前32位和后32位

加锁的意义不仅仅局限于互斥性,还包括内存可见性,为了确保所有线程都能看到共享变量最新值,所有执行读或写操作的线程都必须在同一个锁上同步。

加锁与可见性

image

Volatile

java语言提供了一种稍弱的同步机制,volatile变量,用来确保将变量的更新操作通知到其他线程。

当把变量声明为volatile类型后,编译器与运行时环境都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

重排序:重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段。重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境。

当且仅当满足一下所有条件时,才应该使用volatile变量:

  • 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。

  • 该变量不会与其他状态变量一起纳入不变性条件中。

  • 在访问变量时不需要枷锁

发布与逸出

发布一个对象的意思时指,是对象能够在当前作用域之外的代码中使用。 当某个不应该发布的对象被发布时,这种情况就被称为逸出