多线程中的Java volatile关键字

Posted

技术标签:

【中文标题】多线程中的Java volatile关键字【英文标题】:Java volatile keyword in multithreading 【发布时间】:2018-09-03 17:17:22 【问题描述】:

我试图重现 Java 多线程中的非易失性变量行为。 在这里,我在 OccurrenceCounter.java 类中有非易失性变量 test。 在 ThreadDemo.java 类中,我有 main 产生两个线程的方法。 在thread1 中它不断检查非易失变量test 的值 在while循环中。 如果我在 MyRunnableThread1.java 中运行下面的示例,occurrenceCounter.isTest() 的值不会从 CPU 缓存中获取。

OccurrenceCounter.java:

public class OccurrenceCounter 

    // non-volatile variable
    private   boolean test=true;

    public OccurrenceCounter(boolean test)
    
        this.test=test;
     

    public boolean isTest() 
        return test;
    

    public void setTest(boolean test) 
        this.test = test;
    

MyRunnableThread1.java:

// Thread 1
public class MyRunnableThread1 implements Runnable 

    private String threadName;
    private OccurrenceCounter occurrenceCounter;

    public MyRunnableThread1(String threadName,OccurrenceCounter occurrenceCounter)
    
        this.threadName=threadName; 
        this.occurrenceCounter=occurrenceCounter;
    

    @Override
    public void run() 

        System.out.println("Thread "+threadName + " started");
        System.out.println("value of flag:"+occurrenceCounter.isTest());

        int i=0;
        // random processing code 
        while(i<1000000000L)
        
           i++; 
           i++;
           i++;
        
        // checking whether non-volatile variable is taken from cpu cache
        while(occurrenceCounter.isTest())
        

        
    System.out.println("Thread "+threadName + " finished");
    

MyRunnableThread2.java:

// Thread 2
public class MyRunnableThread2 implements Runnable 

    private String threadName;
    private OccurrenceCounter occurrenceCounter;

    public MyRunnableThread2(String threadName,OccurrenceCounter occurrenceCounter)
    
        this.threadName=threadName; 
        this.occurrenceCounter=occurrenceCounter;
    

    @Override
    public   void run() 

        System.out.println("Thread "+threadName + " started");

        occurrenceCounter.setTest(false);
        System.out.println("Thread "+threadName + " finished");
    

ThreadDemo.java:

public class ThreadDemo 

    public static void main(final String[] arguments) throws InterruptedException 

       System.out.println("main thread started");

       OccurrenceCounter occurrenceCounter =new OccurrenceCounter(true);

       MyRunnableThread1 myRunnableThread1=new MyRunnableThread1("Thread1", occurrenceCounter);
       MyRunnableThread2 myRunnableThread2=new MyRunnableThread2("Thread2", occurrenceCounter);

       Thread t1=new Thread(myRunnableThread1);
       Thread t2=new Thread(myRunnableThread2);

       t1.start();

       try
       
           Thread.sleep(100);
       
       catch(Exception e)
       
           System.out.println("main thread sleep exception:"+e);
       

       t2.start();

       System.out.println("main thread finished");
   

MyRunnableThread1.java 的 while 循环中,即使 OcuurenceCounter 类中的 test 变量是非易失性的,CPU 缓存也不会返回条件 occurrenceCounter.isTest()。 但是如果我删除第一个 while 循环:

while(i<1000000000L)

   i++; 
   i++;
   i++;

然后我可以看到条件occurrenceCounter.isTest() 始终为假,即使它由thread2 更新并且thread1 永远不会终止。 那么为什么会出现这个while循环:

while(i<1000000000L)

   i++; 
   i++;
   i++;

影响非易失性变量的行为? 这是第一个while循环强制从内存而不是thread1的CPU缓存中读取值吗?我试图得到这个答案很多。但我做不到。

请任何人帮助我解决这个问题。

【问题讨论】:

Difference between volatile and synchronized in Java的可能重复 如果没有像 volatilesynchronized 或 java.util.concurrent 类这样的线程安全机制,线程之间的数据的可见性就无法很好地定义。它可能可见也可能不可见。不要依赖你所观察到的;依靠保证。 【参考方案1】:

使用volatile,JMM 可以保证更新后的值可供其他线程使用。

如果没有volatile或其他同步,更新后的值可能或不能可供其他线程使用,这是不确定的。

在这种情况下,循环

while(i<1000000000L) 
    i++; 
    i++;
    i++;

无法保证变量test 的内存可见性,纯属巧合。不要依赖它。

【讨论】:

嗨 user6690200,没有这个 while 循环它工作正常。但是有了这个 while 循环它每次都会失败。我多次尝试过。是否由于某些线程在同一 cpu 核心中运行的方式。因此所有线程都能够从同一个更新的 cpu 缓存中读取值?请就此提供您的输入。谢谢【参考方案2】:

volatile 关键字确保如果一个Thread 对该变量进行了一些更改,那么在更改后读取它的其他Threads 将看到它。如果您使用普通的非volatile 变量,那么其他Threads 可能会也可能不会看到它。这取决于 JVM 的内部结构。

那么,如何解决呢?有几种方法:

    如您所述,您可以将其设为volatile。这可能是最简单的方法:

    public class OccurrenceCounter 
    
        private volatile  boolean test=true;
        // ...
    
    

    您可以将访问器设为synchornized。在这种情况下,您必须同步所有变量访问:

    public class OccurrenceCounter 
    
        private  boolean test=true;
    
        public OccurrenceCounter(boolean test)
        
            this.test=test;
         
    
        public synchronized boolean isTest() 
            return test;
        
    
        public synchronized void setTest(boolean test) 
            this.test = test;
        
    
    

    您可以使用AtomicBoolean 为您处理所有这些。您为此付出的代价是 API 会稍微冗长一些。除非您想使用 compareAndSet()getAndSet() 方法,否则这可能有点过头了:

    private AtomicBoolean test = new AtomicBoolean(true);
    
    public OccurrenceCounter(boolean test)
    
        this.test.set(test);
     
    
    public boolean isTest() 
        return test.get();
    
    
    public void setTest(boolean test) 
        this.test.set(test);
    
    

【讨论】:

以上是关于多线程中的Java volatile关键字的主要内容,如果未能解决你的问题,请参考以下文章

JAVA多线程基础学习三:volatile关键字

java编程,如何彻底理解volatile关键字?

多线程--volatile关键字

Java 并发编程 -- 并发编程线程基础(线程安全问题可见性问题synchronized / volatile 关键字CASUnsafe指令重排序伪共享Java锁的概述)

Java 并发编程 -- 并发编程线程基础(线程安全问题可见性问题synchronized / volatile 关键字CASUnsafe指令重排序伪共享Java锁的概述)

Java中的volatile关键字