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 会把该线程对应的本地内存中的共享变量置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。