声明:尊重他人劳动成果,转载请附带原文链接!学习交流,仅供参考!
文章目录
1、volatile是什么?
1、volatile是一种同步机制,和synchroized、Lock两者之比,更加轻量,因为使用volatile并不会发生
上下文切换
等开销很大的行为。
2、如果一个变量被volatile修饰,那么意味着JVM就知道这个变量可能会被并发修改
。
3、volatile开销很小,对应就会功能也会很小,虽然volatile是用来同步的保证线程安全的,但是volatile却做不到synchronized那样的原子保护,volatile仅在很有限的场景下才能发挥作用。
2、volatile适用于那些场合?
2.1 不适合场景 a++(递增或者递减,不具有原子性)
public class Demo implements Runnable {
volatile int a;
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
// 两个线程对一个共享变量进行并发修改
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("a="+demo.a);
}
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
a++;
}
}
}
结果:
a=142019
假如我们先不看结果的话,我们理想的情况应该是a=200000,而结果怎么却少了这么多次?
有人可能会说,有可能是由于程序只执行了142019次吧,为了验证这一说法,这里我利用了 AtomicInteger
类对代码做了改变。
public class Demo implements Runnable {
AtomicInteger readA = new AtomicInteger();
volatile int a;
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("a=" + demo.a);
System.out.println("readA="+demo.readA.get());
}
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
a++;
readA.incrementAndGet();
}
}
}
结果:
a=193825
readA=200000
看到结果,程序确实运行了200000次,可是为什么a++却少了这么多次呢?
因为我们都知道a++,实际上,它包含了三个独立的操作,读取a的值,将值加1,然后将计算结果写入a,这是一个"读取-修改-写入"的操作序列,如果出现在两个线程在没有同步的情况下,同时对一个计数器执行递增操作,就会出现数值偏差。
2.2 适合场景
2.2.1 赋值操作
如果一个共享变量自始至终只是被各个线程赋值,而没有其他操作,那么就可以用volatile来代替synchronized或者代替原子变量,因为赋值自身是有原子性的,而volatile又保证可见性,所以就足以保证线程安全。
例如:两个线程对共享变量a进行赋值
public class Demo {
volatile int a;
public void change1() {
a = 1;
System.out.println(a);
}
public void change2() {
a = 2;
System.out.println(a);
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.change1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo.change2();
}
}).start();
}
}
2.2.2 触发器
可能大家对触发器不陌生,在MySql里面都是听过的,但这里说的触发器是用作刷新之前变量的触发器。
如下的一个伪代码中, 也说明了volatile作为触发器的场景.
线程A在执行完了一系列的配置操作后, 给volatile修饰的变量initialized赋值了true.
线程B在判断initialized 如果为false则会一直休眠, 直到initialized为true,才会走下面的代码, 并且使用线程初始化的一些配置. 此时 volatile修饰的变量initialized 作为了触发器.
3、volatile的两点作用?
1、可见性:每个线程读一个volatile变量之前,需要先使相应的本地缓存失效,然后从主内存中去读取变量最新的值,然后拷贝到独自的工作空间,修改完变量的值后,会立即刷入到主内存中。
2、禁止指令重排序优化:虽然重排序有好处,但是禁止指令重排序可以解决单例双重锁乱序问题。
4、volatile总结
volatile 在这方面可以看做是轻量版的synchronized,假如一个共享变量自始至终只是被各个线程进行赋值,而没有其他操作,那么就可以用volatile替代synchronized或者代替原子变量,因为赋值本身具有原子性,而volatile又保证可见性,所以就足以保证线程安全。
volatile属性的属性读写操作(
如果只是多个线程对一个共享变量赋值,这条可以忽略
)都是无锁操作的,它不能代替synchronized,因为它不具有原子性和互斥性。正因为没有锁,就不用花费时间在加锁和释放锁上,所以说它是低成本的。
volatile只能用在属性,如果用volatile修饰属性,这样编译器就不会对这个属性进行指令重排序。
volatile也具有可见性
(任何一个线程对一个共享变量修改了值,其他线程都能立即看到)
,也提供了happens-before(前一个写操作执行完后,后一个读操作都能全部知道你写操作干了什么)
保证。