volatile:称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的。
可见:一个线程修改了这个变量的值,在另一个线程中能够读取到这个修改后的值。
synchronized除了线程之间互斥之外,还有一个非常大的作用,就是保证可见性。以下demo即保证a值的可见性。
首先来看demo:
package com.roocon.thread.t7;
public class Demo {
private int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.a = a;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.setA(10);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(demo.getA());
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终结果:" + demo.getA());
}
}
运行结果:
1
最终结果:10
解释:线程1执行set操作,但是,线程2可能在线程1执行set操作成功之前就进行了get操作,这样,get得到的仍然是修改之前的a值
那么,如何保证线程1在执行set操作时,其他线程得到的a值是线程1修改后的值呢?采用同步锁可以实现效果。同步方法上锁的是同一个实例,因此,在执行set方法前,线程1获取了实例锁,那么,其他线程在执行get方法时,必须获得同一把实例锁才可以得到a的值,所以,必须等待线程1释放实例锁后,其他线程才可以继续执行同步的get方法。这样,就可以保证其他线程得到的a值一定是线程1修改后的值。
package com.roocon.thread.t7;
public class Demo {
private int a = 1;
public synchronized int getA() {
return a;
}
public synchronized void setA(int a) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.a = a;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.setA(10);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(demo.getA());
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终结果:" + demo.getA());
}
}
运行结果:
10
最终结果:10
当然,要清楚的一点是,这里的输出统一仅仅是基于线程1的set操作是先于线程2的get操作执行的。但是,要知道,两个线程并发执行,不一定是set操作一定优先get操作执行的。
同样的,volatile也可以保证可见性。因为synchronized是重量级锁,所以,使用volatile会更好。
package com.roocon.thread.t7; public class Demo { private volatile int a = 1; public int getA() { return a; } public void setA(int a) { this.a = a; } public static void main(String[] args) { Demo demo = new Demo(); new Thread(new Runnable() { @Override public void run() { demo.setA(10); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(demo.getA()); } }).start(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("最终结果:" + demo.getA()); } }
运行结果:
10 最终结果:10
再来看个demo理解volatile的可见性:
package com.roocon.thread.t7; public class Demo2 { public volatile boolean run = false; public static void main(String[] args) { Demo2 demo2 = new Demo2(); new Thread(new Runnable() { @Override public void run() { for (int i = 1; i< 5; i++) { System.out.println("执行了第" + i +"次"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } demo2.run = true; } }).start(); new Thread(new Runnable() { @Override public void run() { while(!demo2.run){ } System.out.println("线程2执行了"); } }).start(); } }
运行结果:
执行了第1次 执行了第2次 执行了第3次 执行了第4次 线程2执行了
这里需要注意的是,volatile只能保证线程的可见性,但是并不能保证原子性操作。如果volatile修饰的变量涉及到非原子操作,那么,我们需要使用synchronized来保证它的安全性。
package com.roocon.thread.t7; public class Demo { private volatile int a = 1; public synchronized int getA() { return a++; } public synchronized void setA(int a) { this.a = a; } public static void main(String[] args) { Demo demo = new Demo(); new Thread(new Runnable() { @Override public void run() { demo.setA(10); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(demo.getA()); } }).start(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("最终结果:" + demo.getA()); } }
运行结果:
10 最终结果:11
volatile:
在多处理器的系统上,它会执行一下步骤:
1.将当前处理器缓存行的内容写回到系统内存
2.这个写回到内存的操作会使得在其他CPU里缓存了该内存地址的数据失效
3.其他CPU缓存数据失效,则会重新去内存中读取值,也就是被修改的数据
这里要理解的一个概念是缓存行,缓存行是CPU缓存的一个基本单位。
硬盘---内存--CPU缓存,内存的读取速度比硬盘快,CPU缓存的读取速度比内存更高效。
volatile和synchronized的比较:
synchronized是完全可以替换volatile的,只是volatile相对synchronized是轻量级锁。
volatile是不可以完全替换synchronized的,因为volatile只能保证可见性,并不能保证操作的原子性。所以,很多情况下,两者会相互结合使用。
参考资料:
《java并发编程与实战》龙果学院