您的位置:首页 > 其它

Volatile

2020-04-23 10:05 896 查看

volatile是Java虚拟机提供的轻量级的同步机制。

volatile的特性

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

验证volatile的可见性

不添加volatile的代码:

public class MyData {
/**
* 没有添加volatile关键字
*/
private int number = 0;

public int getNumber() {
return number;
}

public void setNumber(int number) {
this.number = number;
}
}

运行的主线程代码:

/**
* 验证volatile的可见性
*/
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
// 休眠3秒,确保主线程获取到number的值
Thread.sleep(1000 * 3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.setNumber(10);
System.out.println(Thread.currentThread().getName() + "\t update number value: " + myData.getNumber());
}, "AAA").start();
while (myData.getNumber() == 0) {
// main线程就一直循环,直到number值不等于0
}
System.out.println(Thread.currentThread().getName() + "\t mission is over, number value: " + myData.getNumber());
}
}

执行结果:

AAA	 come in
AAA	 update number value: 10

程序一直处于运行状态没有停止,说明主线程处于一直循环中,并且主线程的number值为0;但是AAA线程中number值已经更新为10,说明程序中number值的更新对于其他线程不具有可见性。

添加volatile的代码:

public class MyData {
/**
* 添加volatile关键字
*/
private volatile int number = 0;

public int getNumber() {
return number;
}

public void setNumber(int number) {
this.number = number;
}
}

主程序代码不变
执行结果:

AAA	 come in
AAA	 update number value: 10main	 mission is over, number value: 10

程序会执行结束,AAA线程和主线程的number值都更新为10,说明volatile具有可见性

验证Volatile不保证原子性

原子性:
一个或多个操作要么全部执行完成,要么不执行,并且执行过程中不能被中断。

使用volatile的代码:

public class MyData {
/**
* 添加volatile关键字
*/
private volatile int number = 0;

public int getNumber() {
return number;
}
/**
* 自增方法
*/
public void increment() {
this.number++;
}
}

运行的主线程代码:

/**
* 验证volatile的原子性
*/
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.increment();
}
}, String.valueOf(i + 1)).start();
}
// 等待上面20个线程执行完毕,再用main线程取得最终的结果值
// 程序默认有两个线程,一个是main线程,另一个是后台GC线程
// 所以线程数大于2,代表上面的线程没有执行完毕
while (Thread.activeCount() > 2) {
// 线程礼让,让其他线程执行
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.getNumber());
}
}

执行结果:

main	 finally number value: 19144

20个线程,每个线程对number自增1000,每次执行的结果各不相同,并且都不等于20000,说明被volatile修饰的number不具有原子性。

解决原子性问题

  1. 使用synchronized关键字
  2. 使用原子类
    AtomicInteger类:
    源码:
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}

AtomicInteger有两个构造方法:

  1. AtomicInteger(int initialValue),initialValue为初始值
  2. AtomicInteger(),默认初始值为0

原子操作的方法:

/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
/**
* Atomically decrements by one the current value.
*
* @return the previous value
*/
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
/**
* Atomically decrements by one the current value.
*
* @return the updated value
*/
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}

getAndIncrement()相当于i++;
incrementAndGet()相当于++i;
getAndDecrement()相当于i–;
decrementAndGet()相当于–i;
只不过上面的方法具有原子性。

实现代码

使用原子类的代码

public class MyData {
/**
* 添加volatile关键字
*/
private volatile int number = 0;

public int getNumber() {
return number;
}

AtomicInteger atomicInteger = new AtomicInteger();
/**
* 具有原子性的自增
*/
public void atomicIncrement() {
atomicInteger.getAndIncrement();
}
/**
* 自增方法
*/
public void increment() {
this.number++;
}
}

主线程代码

public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.increment();
myData.atomicIncrement();
}
}, String.valueOf(i + 1)).start();
}
// 等待上面20个线程执行完毕,再用main线程取得最终的结果值
// 程序默认有两个线程,一个是main线程,另一个是后台GC线程
// 所以线程数大于2,代表上面的线程没有执行完毕
while (Thread.activeCount() > 2) {
// 线程礼让,让其他线程执行
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t int type, finally number value: " + myData.getNumber());
System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type, finally number value: " + myData.atomicInteger);
}
}

执行结果:

main	 int type, finally number value: 19354
main	 AtomicInteger type, finally number value: 20000

每次执行完成后,AtomicInteger自增的结果都是20000,说明原子性问题已经解决。

禁止指令重排

volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。

内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:

  1. 保证特定操作的执行顺序
  2. 保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)

由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能对这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。
内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。

对volatile变量进行写操作时:
会在写操作后加入一条

store
屏障指令,将工作内存中的共享变量值刷新回主内存

普通读普通写StoreStore屏障:禁止上面的普通写和下面的volatile写重排序Volatile写StoreLoad屏障:防止上面的volatile写和下面可能有的volatile读/写重排序

对volatile变量进行读操作时:
会在读操作前加入一条load屏障指令,从主内存中读取共享变量

volatile读LoadLoad屏障:禁止下面所有普通读操作和上面的volatile读重排序LoadStore屏障:禁止下面所有的写操作和上面volatile读重排序普通读普通写

线程安全性获得保证

工作内存与主内存同步延迟现象导致的可见性问题
可以使用synchronized或volatile关键字解决,都可以使一个线程修改后的变量立刻对其他线程可见。
对于指令重排导致的可见性问题和有序性问题
可以使用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
yxw678 发布了6 篇原创文章 · 获赞 0 · 访问量 327 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: