Java线程---Volatile最轻量的同步机制
Volatile
接上一篇文章,volatile问题更多在于能否代替synchronized锁,答案是不行的。
想要对比两者关系可以看回上一篇文章 链接: Java线程 synchronized加锁(不能忽视的锁对象).
定义
volatile 具有可见性,保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立刻可见的,但是不能保证原子性,即不能保证在多线程中同时写的一个过程安全。
若是不加volatile关键字,涉及到JDK内部实现、内存模型等问题,其他线程就未必看得见。
Volatile的可见性
上代码看吧,不加volatile的情况(代码思路会放到注释中):
import cn.HYQ.WSQ.XY.Volatile;
/**
* 类说明:演示Volatile的提供的可能性
*/
public class VolatileCase{
private static bollean ready; //声明定义两个类变量
private static int number;
//定义一个线程类
private static class PrintThread extends Thread{
//重写run方法
@Override
public void run{
System.out.println("PrintThread is runing...");
while(!ready); //设置无限循环,只有当ready等于true的时候停止
System.out.println("number = " + number);
}
}
public static void main(String[] args){
Thread thread = new PrintThread();
thread.start(); //启动开始一个线程
SleepTools.second(1);
number = 51; //不加同步机制,子线程没办法看到主线程中对成员变量或者静态变量ready的改变
ready = true;
SleepTools.second(5); //主线程休眠5s
System.out.println("main is ended");
}
}
来看看结果:
很明显的等到主线程结束了子线程都没有输出number 的值,说明 while 到主线程结束都没有停止不限循环,说明 ready 在主线程的改变子线程没有收到消息,所以一直都是 true
改正方法:在类成员对象变量前加上 volatile,保证他的可见性:
public class VolatileCase {
private volatile static boolean ready;
private static int number;
...
}
再来看看结果:
So
不加 volatile 时,子线程无法感知主线程修改了 ready 的值,从而不会退出循环, 而加了 volatile 后,子线程可以感知主线程修改了 ready 的值,迅速退出循环。
但是 volatile 不能保证数据在多个线程下同时写时的线程安全。
volatile的安全性
定义count成员变量并执行 ++,通过两个线程分别执行incCount方法10000次:
package cn.enjoyedu.ch1.vola;
/**
* 类说明:
*/
public class NotSafe {
privatev volatile long count =0; //定义count
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
//count进行累加
public void incCount(){
count++;
}
//定义线程
private static class Count extends Thread{
private NotSafe simplOper;
public Count(NotSafe simplOper) {
this.simplOper = simplOper;
}
//重写run方法
@Override
public void run() {
for(int i=0;i<10000;i++){
simplOper.incCount();
}
}
}
public static void main(String[] args) throws InterruptedException {
NotSafe simplOper = new NotSafe();
//启动两个线程
Count count1 = new Count(simplOper);
Count count2 = new Count(simplOper);
count1.start();
count2.start();
Thread.sleep(50);
System.out.println(simplOper.count);//20000?
}
}
经过两个线程分别执行run方法,累加过后count的值是20000
当没有加上volatile关键字,所以两个线程在对count操作的时候看不到另一个线程同时的执行的结果,所以结果一定大概率不会是20000:
试多几次都不会是20000
现在加上volatile:
public class NotSafe {
private volatile long count =0;
public long getCount() {
return count;
}
...
}
再来看结果:
结果还是不行,就说明了在volatile在安全性上一个很重要的点:
只能保证可见,不保证原子!!!
综上所述,volatile 最适用的场景:一个线程写,多个线程读。
即既然多线程同时写不能保证安全性,那就一个人写,充分利用其可见性,别的线程可读。
《volatile的应用场景后续再补充》