Java并发之volatile关键字
这牵扯到了原子性的问题,所谓原子性,即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
比如如下的赋值语句
String name = "leevare";
将name
赋值为leevare
,这种操作可看做是不可被中断的,那么就是一个原子操作。
那么对于会发生中断的操作会出现什么问题呢?这种问题在多线程中尤为常见。
举个例子,在银行转账,当A用户转账给B用户1000元时,必定会发生这样两个操作,首先,从A的账户上减去1000元,然后,在B的账户上加上1000元。看似这个操作平淡无奇,假如,A本来账户上拥有本金2000元,在A用户转账给B用户1000元后,程序还没有来得及在A账户上减去1000,此时,如果有另外一个用户C在A的账户上来进行取款操作,查询发现余额也是2000元,然后取出2000,在此同时,用户A在转账时查询的结果也是2000,然后进行转账后,系统再减去1000,很明显,这出现了很严重的问题。
出现上面的问题,就是因为两个线程之间对数据操作都是不可见的。线程2在进行扣款时,线程1并不知道发生了扣款操作,这就导致了数据出现了错误。
对于多核CPU而言,每一条线程都有一块自己独占的高速缓存,在执行时,会将执行信息收到读取到自己的高速缓存中。上面的问题就可以看做是这样一种情况,线程一和线程二都从主线程读取了必要信息到自己的高速缓存中,线程一对数据进行了修改,还没有来得及写入主线程,线程二也对其进行了修改,然后再写入主线程,就出现了数据操作的错误。
可以通过使用synchronized
关键字来加锁,将数据操作的方法锁定,让线程保持同步,这时,只能有一个线程才能进行操作,这样,就能解决了多线程间数据不一致的问题。但是,在锁定期间,其余的也有操作该方法的线程都会处于阻塞状态,对于执行效率方面会带来一定的影响。
使用volatile
关键字修饰的变量,表示该变量在多线程操作之间是透明可见的,修改之后,其余的线程也能立即感知,就解决了数据变化的问题。
为什么是透明可见呢?
使用volatile
修饰后,会强制将修改的值立即写入主存。同样是上面的问题,两个线程同时读取了必要的信息到自己的高速缓存中,线程一如果对数据(比如说金额是count
)进行了修改后,立即将count
写入到主线程,会导致线程二中原来读取的count
立即失效,这时候线程二会重新从主线程中读取数据,此时,线程二就会读取到正确的数据,从而保证了数据的同步。
例如如下的例子
public class TestMain {
public static void main(String[] args) {
TestThread testThread = new TestThread();
new Thread(testThread, "t1").start();
new Thread(testThread, "t2").start();
}
}
class TestThread implements Runnable {
private volatile boolean stop = false;
public void run() {
if ("t1".equals(Thread.currentThread().getName())) {
while (!stop) {
System.out.println(111);
}
} else if ("t2".equals(Thread.currentThread().getName())) {
stop = true;
}
}
}
这里会输出111
一次,是因为第二个线程修改了stop
之后,第一个线程也能立即感知,就不会出现死循环的情况。反之,如果将volatile
关键字去除后,就会出现死循环了。
- 本博客所有文章除特别声明外,均可转载和分享,转载请注明出处!
- 本文地址:https://www.leevii.com/?p=1276