1

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

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");
    }
}

线程演示.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");
   }
}

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

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

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

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

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

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

4

2 回答 2

2

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

如果没有volatile或其他同步,更新的值是否可以用于其他线程,这是不确定的。

在这种情况下,循环

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

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

于 2018-03-25T13:59:49.820 回答
0

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

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

  1. 正如你所提到的,你可以做到volatile。这可能是最简单的方法:

    public class OccurrenceCounter {
    
        private volatile  boolean test=true;
        // ...
    }
    
  2. 你可以制作访问器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;
        }
    }
    
  3. 您可以使用一个AtomicBoolean为您处理所有这些的。您为此付出的代价是 API 会稍微冗长一些。这可能是一个矫枉过正,除非你想使用compareAndSet()orgetAndSet()方法:

    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);
    }
    
于 2018-03-26T10:03:00.940 回答