Java并发编程:volatile

package com.juc.test;

public class NumberTest {
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();

        new Thread(() -> {
            System.out.println("********** A start **********");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myNumber.changeNumber();
            System.out.println(Thread.currentThread().getName() + " has updated number, number value is " + myNumber.number);
        }, "A").start();

        while (myNumber.number == 10) {
            //需要一种通知机制告诉main线程,number已经修改为1024,跳出while循环
        }

		//获取当前线程的线程状态
        System.out.println(Thread.currentThread().getState()); //RUNNABLE

        System.out.println(Thread.currentThread().getName() + " mission is over.");
    }
}

class MyNumber {
    volatile int number = 10;

    public void changeNumber() {
        this.number = 1024;
    }
}
  • volatile 关键字能够保证内存的可见性,如果用 volatile 关键字声明了一个变量,在一个线程里面改变了这个变量的值,那其它线程是立马可见更改后的值的。
  • volatile 关键字是Java虚拟机提供的轻量级的同步机制。
  • volatile 的三个特性:
    • 保证可见性:对一个 volatile 变量的读,总是能看到任意线程对这个 volatile 变量最后的写入。
    • 不保证原子性:对任意单个 volatile 变量的读/写具有原子性,对 volatile 变量的复合操作不具有原子性。
    • 禁止指令重排:编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
      • 在每个volatile写操作前面插入一个StoreStore屏障;
      • 在每个volatile写操作后面插入一个StoreLoad屏障;
      • 在每个volatile读操作前面插入一个LoadLoad屏障;
      • 在每个volatile读操作后面插入一个LoadStore屏障。

原子性就是不可分割、完整性,也即某个线程正在做某个具体业务的时候,中间不可以被加塞或者被分割,需要整体完整。也就是说,原子操作是指不可被中断的一个或一系列操作

  • 原子性是指在一个操作中CPU不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
  • 在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。
  • 解决原子性问题的方法:
    • synchronized(给方法上锁)
    • 使用原子类(如:JUC下的AtomicInteger类)
  • 编写的代码在JVM执行的时候,为了提高性能,编译器和处理器都会对代码编译后的指令进行重排序。分为3种:
  • 编译器优化的重排序:编译器的优化前提是在保证不改变单线程语义的情况下,重新安排语句的执行顺序。
  • 指令级并行的重排序:如果代码中某些语句之间不存在数据依赖,处理器可以改变语句对应机器指令的顺序。
  • 内存系统的重排序:处理器和主内存之间还存在一二三级缓存。这些读写缓存的存在,使得程序的加载和存取操作,可能是乱序无章的。
  • 在单线程的条件下,指令重排不会影响到最终的结果,也就是数据的一致性可以得到保证;但是在多线程的条件下,各个线程交替执行,两个线程间使用的变量能否保证一致性就不能得到保证。

volatile 的写-读内存语义:

  • 当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。
  • 当读一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值置为无效,然后从主内存中读取共享变量。

总结:

  • 线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
  • 线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的消息。
  • 线程A写一个volatile变量,随后线程B读一个volatile变量,这个过程实质上是线程A通过主内存向线程B发送了消息。
  • volatile 的写-读与锁的释放-获取有相同的内存语义。

锁的释放-获取的内存语义:

  • 当线程释放锁时,JMM 会把该线程对应的本地内存中的共享变量刷新到主内存中。
  • 当线程获取锁时,JMM 会把该线程对应的本地内存中的共享变量置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值