volatile关键字

volatile保证变量的可见性

可见性与Java的内存模型有关,模型采用缓存与主存的方式对变量进行操作,也就是说,每个线程都有自己的缓存空间,对变量的操作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程在将共享变量修改后,还没有来的及将缓存中的变量返回给主存中,另外一个线程就对共享变量进行修改,那么这个线程拿到的值是主存中未被修改的值,这就是可见性的问题。

volatile很好的保证了变量的可见性,变量经过volatile修饰后,对此变量进行写操作时,汇编指令中会有一个LOCK前缀指令,这个不需要过多了解,但是加了这个指令后,会引发两件事情:

  • 将当前处理器缓存行的数据写回到系统内存
  • 这个写回内存的操作会使得在其他处理器缓存了该内存地址无效

什么意思呢?意思就是说当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,这就保证了可见性

volatile可见性测试

不加volatile

线程1start之后一直空转,陷入死循环,因为线程1读取的一直是自己内存的副本数据,线程1并不知道要去主内存拉取新数据


public class Main extends Thread {
    static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            System.out.println("start");
            while (flag) {

            }
            System.out.println("end");
        }, "线程1").start();

        TimeUnit.SECONDS.sleep(3);

        flag = false;
    }
}

加上volatile

volatile关键字的修饰 使得一旦flag被修改,将会通知其他副本内存同步新的flag,while即可以在一定时间内结束

public class Main extends Thread {
    static volatile  boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            System.out.println("start");
            while (flag) {
                
            }
            System.out.println("end");
        }, "线程1").start();

        TimeUnit.SECONDS.sleep(3);

        flag = false;
    }
}

奇怪的事情

不加volatile ,按理说会一直while循环,但是在while里面加上一些代码之后,程序也会在一定时间之后结束。我并不知道问什么!

猜想可能是while循环有代码时,内存数据切换比较频繁,导致数据同步频率升高,这样的话就能读到新数据了

public class Main extends Thread {
    static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            System.out.println("start");
            while (flag) {
                System.out.println("beauty");
            }
            System.out.println("end");
        }, "线程1").start();

        TimeUnit.SECONDS.sleep(3);

        flag = false;
    }
}

why

println方法中, 使用到了synchronized

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

volatile屏蔽指令重排序

指令重排序是编译器和处理器为了高效对程序进行优化的手段,它只能保证程序执行的结果时正确的,但是无法保证程序的操作顺序与代码顺序一致。这在单线程中不会构成问题,但是在多线程中就会出现问题。非常经典的例子是在单例方法中同时对字段加入voliate,就是为了防止指令重排序

双重锁的volatile作用

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) { // 1
            synchronized(Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton(); // 2
                }
            }
        }
        return singleton;
    }
} 

实际上当程序执行到2处的时候,如果我们没有使用volatile关键字修饰变量singleton,就可能会造成错误。这是因为使用new关键字初始化一个对象的过程并不是一个原子的操作,它分成下面三个步骤进行:

a. 给 singleton 分配内存
b. 调用 Singleton 的构造函数来初始化成员变量
c. 将 singleton 对象指向分配的内存空间(执行完这步 singleton 就为非 null 了)

如果虚拟机存在指令重排序优化,则步骤b和c的顺序是无法确定的。如果A线程率先进入同步代码块并先执行了c而没有执行b,此时因为singleton已经非null。这时候线程B到了1处,判断singleton非null并将其返回使用,因为此时Singleton实际上还未初始化,自然就会出错。synchronized可以解决内存可见性,但是不能解决重排序问题

指令重排程序测试

public class Main extends Thread {
    static  int a = 0;
    static volatile int x = 0;

    static  int b = 0;
    static  int y = 0;

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            a = 0;
            x = 0;
            b = 0;
            y = 0;

            Thread first = new Thread(() -> {
                a = 1;
                x = b;
            });

            Thread second = new Thread(() -> {
                b = 1;
                y = a;
            });

            first.start();
            second.start();
            first.join();
            second.join();

            if (x == 0 && y == 0) {
                System.out.println("x=" + x + " y=" + y);
                break;
            } else {
                System.out.println("x=" + x + " y=" + y);
            }
        }
    }
}

讲道理,上述代码可能的x,y值有:01,10,11,但应该不能是00

测试结果表明,未加上volatile会出现00,加上就不会出现。这就表明a=1,x=b的顺序被换了,即多线程中未加volatile时会进行指令重排,造成数据有误。

volatile为什么不具备原子性?

首先需要了解的是,Java中只有对基本类型变量的赋值和读取是原子操作,如i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。

所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性了。

一个变量i被volatile修饰,两个线程想对这个变量修改,都对其进行自增操作也就是i++,i++的过程可以分为三步,首先获取i的值,其次对i的值进行加1,最后将得到的新值写会到缓存中。
线程A首先得到了i的初始值100,但是还没来得及修改,就阻塞了,这时线程B开始了,它也得到了i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是100,之后对其进行加1操作,得到101后,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。
问题来了,线程A已经读取到了i的值为100,也就是说读取的这个原子操作已经结束了,所以这个可见性来的有点晚,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存,所以即便是volatile具有可见性,也不能保证对它修饰的变量具有原子性。

AtomicInteger : volatile与cas结合确保原子性

结合上文volatile的原子性阐述,如果线程A修改的时候采用CAS方式修改,即能判断到主内存的i=101值与预期值100不相等,则要么此次更新失败,要么将101写回到线程A的本地内存进行加法CAS操作。AtomicInteger即是采用了volatile和compare and swap的方式操作的。

Java 中能创建 volatile 数组吗?

能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组。意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了

volatile 变量和 atomic 变量有什么不同?

Volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前,但它并不能保证原子性。例如用volatile修饰count变量那么count++操作就不是原子性的。
而AtomicInteger类提供的atomic方法可以让这种操作具有原子性
如getAndIncrement( )方法会原子性的进行增量操作把当前值加- ,其它数据类型和引用变量也可以进行相似操作

synchronized 和 volatile 的区别是什么?

作用:

  • synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
  • volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。

区别:

  • synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。
  • synchronized 可以保证线程间的有序性(猜测是无法保证线程内的有序性,即线程内的代码可能被 CPU 指令重排序)、原子性和可见性;volatile 只保证了可见性和有序性,无法保证原子性。
  • synchronized 线程阻塞,volatile 线程不阻塞。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
智慧校园建设方案旨在通过融合先进技术,如物联网、大数据、人工智能等,实现校园的智能化管理与服务。政策的推动和技术的成熟为智慧校园的发展提供了基础。该方案强调了数据的重要性,提出通过数据的整合、开放和共享,构建产学研资用联动的服务体系,以促进校园的精细化治理。 智慧校园的核心建设任务包括数据标准体系和应用标准体系的建设,以及信息化安全与等级保护的实施。方案提出了一站式服务大厅和移动校园的概念,通过整合校内外资源,实现资源共享平台和产教融合就业平台的建设。此外,校园大脑的构建是实现智慧校园的关键,它涉及到数据中心化、数据资产化和数据业务化,以数据驱动业务自动化和智能化。 技术应用方面,方案提出了物联网平台、5G网络、人工智能平台等新技术的融合应用,以打造多场景融合的智慧校园大脑。这包括智慧教室、智慧实验室、智慧图书馆、智慧党建等多领域的智能化应用,旨在提升教学、科研、管理和服务的效率和质量。 在实施层面,智慧校园建设需要统筹规划和分步实施,确保项目的可行性和有效性。方案提出了主题梳理、场景梳理和数据梳理的方法,以及现有技术支持和项目分级的考虑,以指导智慧校园的建设。 最后,智慧校园建设的成功依赖于开放、协同和融合的组织建设。通过战略咨询、分步实施、生态建设和短板补充,可以构建符合学校特色的生态链,实现智慧校园的长远发展。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值