volatile定义
volatile是java虚拟机提供的轻量级的同步机制
特性
保证可见性
不保证原子性
禁止指令重排
示例
在不使用volatile的情况下,主线程获取的num和子线程中设置数值的num存放在不同位置的,彼此不可见,因此,在子线程中设置num的值时,主线程中的num并不会被改变,依然是num=0
public class VolatileTest {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 修改前的值:"+myData.num);
//休眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改数值
myData.setNum();
System.out.println("修改后的值:"+myData.num);
}).start();
while (myData.num == 0){
}
System.out.println(Thread.currentThread().getName()+"执行完");
}
}
class MyData{
int num = 0;
public synchronized void setNum() {
num = 123;
}
}
运行结果:
Thread-0 修改前的值:0
修改后的值:123
(循环)
但是加了volatile之后,使num成员变量存储在了内存中,增加了num的可见性,因此使得主线程能够访问到子线程设置的num,从而能退出循环,继续执行
public class VolatileTest {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 修改前的值:"+myData.num);
//休眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改数值
myData.setNum();
System.out.println("修改后的值:"+myData.num);
}).start();
while (myData.num == 0){
}
System.out.println(Thread.currentThread().getName()+"执行完");
}
}
class MyData{
volatile int num = 0;
public synchronized void setNum() {
num = 123;
}
}
运行结果:
Thread-0 修改前的值:0
修改后的值:123
main执行完
But
如果在while循环中加入了输出语句,结果就不一样了
以不加volatile为例
public class VolatileTest {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 修改前的值:"+myData.num);
//休眠
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改数值
myData.setNum();
System.out.println("修改后的值:"+myData.num);
}).start();
while (myData.num == 0){
System.out.println("正在循环");
}
System.out.println(Thread.currentThread().getName()+"执行完");
}
}
class MyData{
int num = 0;
public synchronized void setNum() {
num = 123;
}
}
运行结果:
...
正在循环
正在循环
修改后的值:123
main执行完
此时会发现在不加volatile的情况下,程序竟然结束了循环!也就是说,主线程在经过了1秒后竟然能够访问子线程中修改后的num值了,而不是之前的死循环。
这又是为什么呢?
通过查看System.out.println(); 这个输出语句的底层源码可见
/**
* Prints a String and then terminate the line. This method behaves as
* though it invokes <code>{@link #print(String)}</code> and then
* <code>{@link #println()}</code>.
*
* @param x The <code>String</code> to be printed.
*/
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
好家伙,直接在println方法中同步了PrintStream 对象,当我们调用输出方法时,主线程和子线程中的num值得到了同步,使得产生了一种,好像不用volatile也能实现主线程访问子线程修改后的num的感觉。
因此在此得到了解释。