1.概述
-
Volatile称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的。
-
可见:一个线程修改了这个变量的值,在另外一个线程中能够读到这个修改后的值。也就是读写可见.
-
volatile不保证原子性
-
Synchronized除了线程之间互斥意外,还有一个非常大的作用,就是保证可见性
2.示例
2.1 多线程set和get结果不一致
/**
*
* 多线程一个线程set,一个线程get,会造成set的结果在get中看不到.
*/
public class VolatileThread1_NotVolatile {
public int value = 1;
public int getValue() {
return value;
}
public void setValue(int value) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = value;
}
/**
为了保证setValue后执行,所以sleep了20ms
*/
public static void main(String[] args) throws InterruptedException {
VolatileThread1_NotVolatile volatileThread1_NotVolatile = new VolatileThread1_NotVolatile();
new Thread(new Runnable() {
@Override
public void run() {
volatileThread1_NotVolatile.setValue(10);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("get线程得到的结果为:" + volatileThread1_NotVolatile.getValue());
}
}).start();
Thread.sleep(1000);
System.out.println("最终的结果为:" + volatileThread1_NotVolatile.getValue());
}
}
2.2 通过synchronized不能一定解决set get同步问题
/**
* 保证可见性的前提: 多个线程拿到的是同一把锁,否则是保证不了的。
* 多线程一个线程set,一个线程get,会造成set的结果在get中看不到.
* <p>
* 通过synchronized能同步实例,实现set结果能同步
*/
public class VolatileThread2_Synchronized {
public int value = 1;
public synchronized int getValue() {
return value;
}
public synchronized void setValue(int value) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = value;
}
/**
由于synchronized
1)可能执行完主线程的get(),然后才进入锁实例进行set(),就是1
2)可能先锁实例set(),然后才执行主线程的get(),那就是10
注:synchronized只是能阻止两个线程同时执行set()或get(),不能左右线程的顺序.
根据原因是没有锁属性value,而通过volatile可以实现属性可见性.
*/
public static void main(String[] args) {
VolatileThread2_Synchronized volatileThread2_Synchronized = new VolatileThread2_Synchronized();
new Thread(new Runnable() {
@Override
public void run() {
volatileThread2_Synchronized.setValue(10);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("get线程的结果为:" +volatileThread2_Synchronized.getValue());
}
}).start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终的结果为:" + volatileThread2_Synchronized.getValue());
}
}
2.3 通过volatile来解决属性(变量)的线程间可见
public class VolatileThread3_Volatile {
//保证变量在多个线程的值的一致性
public volatile int value = 1;
/**
新线程的结果为:10
最终的结果为:10
*/
public static void main(String[] args) {
VolatileThread3_Volatile volatileThread3_volatile = new VolatileThread3_Volatile();
//保证set值先执行,然后另一个线程能获取到结果
volatileThread3_volatile.value = 10;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程的结果为:"+volatileThread3_volatile.value);
}
}).start();
System.out.println("最终的结果为:" + volatileThread3_volatile.value);
}
}
2.4 volatile的使用场景
/**
* volatile的使用场景
* 任务1多次之后才改变值,
* 任务2一直判断任务1的值,当任务1一改变值,任务2就得到任务1的结果了并使用.
*/
public class VolatileThread4_Volatile {
public volatile boolean run = false;
/**
执行了第 1 次
执行了第 2 次
执行了第 3 次
执行了第 4 次
执行了第 5 次
线程2执行了...
*/
public static void main(String[] args) {
VolatileThread4_Volatile volatileThread4_volatile = new VolatileThread4_Volatile();
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();
}
}
volatileThread4_volatile.run = true;
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(!volatileThread4_volatile.run) {
// 不执行
}
System.out.println("线程2执行了...");
}
}).start();
}
}
2.5 synchronized替代volatile实现volatile的使用场景
synchronized替代volatile实现volatile的使用场景,但是volatile有轻量化,并简洁.以下使用VolatileThead4实现VolatileThead3同样的功能.
**
* 用synchronized来替代VolatileThead3的volatile的使用场景
* 任务1多次之后才改变值,
* 任务2和3一直判断任务1的值,当任务1一改变值,任务2就得到任务1的结果了并使用.
*/
public class VolatileThread5_Synchronized {
public boolean run = false;
public synchronized boolean isRun() {
return run;
}
public synchronized void setRun(boolean run) {
this.run = run;
}
/**
执行了第 1 次
执行了第 2 次
执行了第 3 次
执行了第 4 次
执行了第 5 次
线程3最终执行了...
*/
public static void main(String[] args) {
VolatileThread5_Synchronized volatileThread5_synchronized = new VolatileThread5_Synchronized();
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();
}
}
//d.run = true;
volatileThread5_synchronized.setRun(true);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(!volatileThread5_synchronized.run) {
// 不执行
//System.out.println("线程2这就执行了");
}
System.out.println("线程2最终执行了...");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(!volatileThread5_synchronized.isRun()) {
// 不执行
//System.out.println("线程3这就执行了");
}
System.out.println("线程3最终执行了...");
}
}).start();
}
}
3. volatile底层原理
-
Lock指令
-
- 在多处理器的系统上,将当前处理器缓存行(cpu缓存的最小单位)的内容写回到系统内存,
-
- 这个写回到内存的操作会使在其他CPU里缓存了该内存地址的数据失效(所以volatile实现一个线程改变,让其它线程得到的当前内容失效,并且将当期线程的结果回写)
-
存储等级 硬盘 --> 内存 --> CPU的缓存(L1,L2等)
-
多线程在多cpu处理数据时,会将内存中的数据缓存到处理器缓存中,而在cpu处理完之后而不是立刻回写到内存中去.
那么在某个cpu中处理的数据,在另一个cpu中就是不可见的.所以如果需要同步内容,就加volatile,内容立刻回写并让其它的失效.
-
如果大量使用volatile,则会造成大量处理器的缓存失效(减少CPU的性能优化),使用会影响性能. 所以要根据具体场景选择使用.
-
volatile还有影响指令重排序.
4.synchronized和volatile
-
synchronized 保证数据的原子性操作
-
volatile 只是保证可见性,不保证数据的原子性(比如如果getA()中包含有a++则volatile使用无意义),保证原子性的可见性
-
synchronized能取代volatile,但是volatile不能取代synchronized,但是volatile有轻量化,并简洁.