在java中有一种比synchronized
更轻量级的同步方式,那就是volatile
关键字。不过他功能并没有那么强大.在这里我写一下volatile
关键字与synchronized
关键字的区别:
- volatile在多线程中只能保证数据的可见性,synchronized关键字可见性和原子性都能得到保证。
- volatile关键字只能修饰变量,synchronized可以修饰方法,代码块…
- volatile不会阻塞线程,synchronized会。
- volatile还有阻止”指令重排序”的能力,什么是指令重排序大家可以搜一搜,通俗的说就是编译器在编译好我们的代码后,程序的运行顺序不一定是按照我们写代码的顺序运行的,但这是在不会影响最后结果的情况下。
那么对于可见性和原子性怎么理解呢,我们首先来看看java的一个内存模型:
原子性
线程在对共享数据进行操作时会先拉一个副本进自己私有的内存空间进行操作,然后操作完了之后在把数据写会主内存去,比如一个i++
的操作就是:
- 从主存读取i的值
- 对i进行++的操作
- 将结果写回主存
所以类似i++这种并不是原子操作,原子操作是类似int a = 5
这样的。
可见性
一个线程修改的变量的值,能立即被其他的线程看见。这就是可见性,很简单.
volatile的可见性如何解释
我们用代码来说明,首先来看一段代码:
这里注意代码要用jvm的server模式来运行,不用client模式来运行.
server模式启动慢,运行快.是用于服务器的
client模式启动快,运行慢,用于桌面应用客户端
所以server模式会为了速度,在线程中,除非线程结束,否则是不会去主存读取数据的,所以我们用server模式更容易看到效果
class Test1 {
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void m1() {
/**
* 如果flag为true,则无限循环
* 此处虽然在main方法中由另一个线程调用了stop()方法,可是在server模式下,该线程一直使用的是
* 自己的私有内存中的flag值,导致程序陷入死循环
*/
while (flag) {}
System.out.println("我结束了");
}
/**
* 将flag改成false的方法
*/
public void stop() {
flag = false;
}
}
class Thread3 extends Thread {
private Test1 test1;
public Thread3(Test1 test1) {
this.test1 = test1;
}
@Override
public void run() {
test1.m1();
}
}
class Thread4 extends Thread {
private Test1 test1;
public Thread4(Test1 test1) {
this.test1 = test1;
}
@Override
public void run() {
test1.stop();
}
}
public class SynchronizedTest2 {
public static void main(String[] args) throws InterruptedException {
Test1 t = new Test1();
Thread3 t3 = new Thread3(t);
Thread4 t4 = new Thread4(t);
t3.start();
Thread.sleep(1000);
t4.start();
}
}
将flag变成volatile后:
private volatile boolean flag = true;
...
// 这时程序会正常结束.
这时程序正常结束的原因是因为volatile变量的读写操作都会强制去主存中进行
也可以使用synchronized代码块来实现效果:
while (flag) {
synchronized (this){
}
}
这段代码也会得到同样运行成功的结果,因为synchronized代码块会间接的保证可见性,也保证原子性。因为synchronized代码块结束之后会主动同步主存与线程私有内存的数据