您的位置:首页 > 其它

volatile关键字解析(二)

2018-01-22 16:26 148 查看
volatile详解
接下来,我们详细讲述一下volatile关键字
volatile关键字具有两重语义

保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这个新值对其他线程来说是立即可见的。

禁止指令重排序

依然使用上文RunThread案例说明,上文中已经提到了,由于主线程修改了isRunning的值后,RunThread线程并没有看到其值的变化,因此,进入了死循环,但是当isRunning加上volatile关键字后,效果就不一样了。

使用volatile关键字会强制将修改的值立即写入主存;

使用volatile关键字的话,当主线程修改时,会导致RunThread的工作内存中isRunning变量的缓存值变得无效。

由于RunThread的工作内存中缓存变量isRunning缓存无效,所以会再次从主存中读取isRunning变量值。

这样就保证了RunThread可以顺利结束。

关于原子性,我们先看个例子:

public class TestCount
{
private volatile int n;

public void increase()
{
n ++;
}

public static void main(String[] args)
{
TestCount count = new TestCount();
for(int i=0; i<10; i++)
{
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
count.increase();
};
}.start();
}

if(Thread.activeCount() > 1)
{
Thread.yield();
}
System.out.println(count.n);
}
}


关于这个结果,应该很多人会认为是10000吧。但是实际上,运行结果小于10000,这是由于n++操作并非原子性操作,其涉及三个操作

读出n的旧知

对n+1

写入新值

虽然volatile可以保证每个线程读取到的是最新的内存值,但是,如果10个线程第一步都是正确的,第二步和第三步单线程看的话也都是没有问题的,但是如果并发的话,就会导致同一个值被多次写入了内存。
可见volatile并不能保证原子性操作。
对于这一类问题,我们可以通过synchronized、lock或者原子操作类经行操作。
synchronized版本:

public class TestCount
{
public  int inc = 0;

public synchronized void increase() {
inc++;
}

public static void main(String[] args) {
final TestCount test = new TestCount();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}

while(Thread.activeCount()>1)  //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}


lock版本:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestCount
{
public  int inc = 0;
Lock lock = new ReentrantLock();

public  void increase() {
lock.lock();
try {
inc++;
} finally{
lock.unlock();
}
}

public static void main(String[] args) {
final TestCount test = new TestCount();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}

while(Thread.activeCount()>1)  //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}


原子操作类版本:

import java.util.concurrent.atomic.AtomicInteger;

public class TestCount
{
public  AtomicInteger inc = new AtomicInteger();

public  void increase() {
inc.getAndIncrement();
}

public static void main(String[] args) {
final TestCount test = new TestCount();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}

while(Thread.activeCount()>1)  //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}


对于有序性,我们可以看下下面的这个例子:

//线程1:
context = loadContext();   //语句1
inited = true;             //语句2

//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);


如果没有对inited加volatile关键字,则可能会对语句1和语句2进行调整,因此两者并无依赖关系,并且单线程下,也并不会影响结果。但是两个线程下,可能由于context并没有初始化完成,导致意想不到的结果。
如果对inited加上volatile,这个问题就不会发生了。语句1和语句2的执行顺序则不会被调整。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: