Java中volatile关键字理解

Java中volatile关键字理解,第1张

Java中volatile关键字理解 1、Volatile定义

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1、保证了不同线程对这个变量进行 *** 作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2、禁止进行指令重排序。
先看一段代码,假如线程1先执行,线程2后执行:

// 线程1
boolean stop = false;
while(!stop){
    doSomething();
}
// 线程2
stop = true;

这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程。
在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
但是用volatile修饰之后就变得不一样了:
第一,使用volatile关键字会强制将修改的值立即写入主存
第二,使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效)
第三,由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。

2、Volatile能保证原子性吗?

先看代码:

public class Test {
	// volatile修饰
    public volatile int inc = 0;
     
    public void increase() {
        inc++;
    }
     
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<500;j++)
                        test.increase();
                };
            }.start();
        }
         
       try {
       		Thread.sleep(3000);
       } catch(Exception e) {
       }
       System.out.println(test.inc);
    }
}

上述代码的执行结果是一个不确定的数,是一个小于等于5000的数。为什么?因为inc++是不保证原子性的 *** 作,++ *** 作它包括读取变量的原始值、进行加1 *** 作、写入工作内存三步流程。假设线程A读取了inc变量的原始值,虽然是volatile,那就从主内存里直接读咯,读取完成后,这个时候线程A时间片走完,别的线程这个时候把这个值+1了,再回到线程A的时候,再对旧值+1,所以重复了,因此会得到比实际值5000小的值。
这里面就有一个误区了,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的 *** 作的原子性。
不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面说的volatile变量规则,但是要注意,线程A对变量进行读取 *** 作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。

怎么改进呢?
第一种,synchronized

public class Test {
	// volatile修饰
    public volatile int inc = 0;
    // 加上synchronized关键字
    public synchronized void increase() {
        inc++;
    }
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<500;j++)
                        test.increase();
                };
            }.start();
        }
         
       try {
       		Thread.sleep(3000);
       } catch(Exception e) {
       }
       System.out.println(test.inc);
    }
}

第二种,采用锁lock

public class Test {
	// volatile修饰
    public volatile int inc = 0;
   Lock lock = new ReentrantLock();
   
    public void increase() {
       lock.lock(); // 加锁
        try {
            inc++;
        } finally{
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<500;j++)
                        test.increase();
                };
            }.start();
        }
         
       try {
       		Thread.sleep(3000);
       } catch(Exception e) {
       }
       System.out.println(test.inc);
    }
}

第三种,使用原子 *** 作类Atomic

public class Test {
	// 原子 *** 作类,底层使用CAS来进行原子性 *** 作
    public  AtomicInteger inc = new AtomicInteger();
     
    public  void increase() {
        inc.getAndIncrement();
    }
    
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<500;j++)
                        test.increase();
                };
            }.start();
        }
        
       try {
       		Thread.sleep(3000);
       } catch(Exception e) {
       }
       System.out.println(test.inc);
    }
}
3、Volatile能保证有序性吗?
// x、y为非volatile变量
// flag为volatile变量
x = 2;        //语句1
y = 0;        //语句2
flag = true;  //语句3
x = 4;         //语句4
y = -1;       //语句5

由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。

4、Volatile应用
// DCL单例模式
public class PersonLazy4 {
	// 加上volatile关键字,最值得推荐的方式
	private static volatile PersonLazy4 mInstance = null;
	private PersonLazy4() {}
	
	public static PersonLazy4 getInstance() {
		if (mInstance == null) {
			synchronized (PersonLazy4.class) {
				if (mInstance == null) {
					mInstance = new PersonLazy4();
				}
			}
		}
		return mInstance;
	}
}

部分内容转载于:http://www.cnblogs.com/dolphin0520/p/3920373.html

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5683879.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存