并发:缓存一致性问题还是编译器优化?

vik*_*.rk 1 java concurrency multithreading pthreads javac

根据我的理解,如果硬件支持多处理器系统上的高速缓存一致性,则对其他处理器上运行的线程可以看到对共享变量的写入.为了测试这个,我用Java和pThreads编写了一个简单的程序来测试它

public class mainTest {

    public static int i=1, j = 0;
    public static void main(String[] args) {

    /*
     * Thread1: Sleeps for 30ms and then sets i to 1
     */
    (new Thread(){
        public void run(){
            synchronized (this) {
                try{
                       Thread.sleep(30);
                       System.out.println("Thread1: j=" + mainTest.j);
                       mainTest.i=0;
                   }catch(Exception e){
                       throw new RuntimeException("Thread1 Error");
                }
            }
        }
    }).start();

    /*
     * Thread2: Loops until i=1 and then exits.
     */
    (new Thread(){
        public void run(){
            synchronized (this) {
                while(mainTest.i==1){
                    //System.out.println("Thread2: i = " + i); Comment1
                    mainTest.j++;
                }
                System.out.println("\nThread2: i!=1, j=" + j);
            }
        }
    }).start();

   /*
    *  Sleep the main thread for 30 seconds, instead of using join. 
    */
    Thread.sleep(30000);
    }
}




/* pThreads */

#include<stdio.h>
#include<pthread.h>
#include<assert.h>
#include<time.h>

int i = 1, j = 0;

void * threadFunc1(void * args) {
    sleep(1);
    printf("Thread1: j = %d\n",j);
    i = 0;
}

void * threadFunc2(void * args) {
while(i == 1) {
        //printf("Thread2: i = %d\n", i);
        j++;
    }
}

int main() {
    pthread_t t1, t2;
    int res;
    printf("Main: creating threads\n");

    res = pthread_create(&t1, NULL, threadFunc1, "Thread1"); assert(res==0);
    res = pthread_create(&t2, NULL, threadFunc2, "Thread2"); assert(res==0);

    res = pthread_join(t1,NULL); assert(res==0);
    res = pthread_join(t2,NULL); assert(res==0);

    printf("i = %d\n", i);
    printf("Main: End\n");
    return 0;
}    
Run Code Online (Sandbox Code Playgroud)

我注意到pThread程序总是结束.(我为thread1测试了不同的睡眠时间).然而,Java程序只结束了几次; 大部分时间都没有结束.如果我在java程序中取消注释Comment1,那么它会一直结束.此外,如果我使用volatile,那么在所有情况下它都会以java结尾.

所以我的困惑是,

  1. 如果在硬件中完成高速缓存一致性,那么除非编译器对代码进行优化,否则"i = 0"应该对其他线程可见.但是如果编译器优化了代码,那么我不明白为什么线程有时会结束而有时不会结束.另外,添加System.out.println似乎会改变行为.

  2. 任何人都可以看到Java所做的编译器优化(这不是由C编译器完成的),这会导致这种行为吗?

  3. 还有一些额外的编译器必须做的事情,即使硬件已经支持它,也能获得Cache一致性吗?(如启用/禁用)

  4. 我应该默认使用Volatile用于所有共享变量吗?

我错过了什么吗?欢迎提出任何其他意见.

Pet*_*rey 5

如果在硬件中完成高速缓存一致性,那么除非编译器对代码进行优化,否则"i = 0"应该对其他线程可见.但是如果编译器优化了代码,那么我不明白为什么线程有时会结束而有时不会结束.另外,添加System.out.println似乎会改变行为.

注意:javac没有优化,所以不要考虑静态优化.

您正在锁定与您正在修改的对象无关的不同对象.由于您正在修改的字段不是volatileJVM优化器可以随意动态优化它,无论您的硬件可以提供哪种支持.

由于这是动态的,它可能会也可能不会优化您在该线程中不更改的字段的读取.

任何人都可以看到Java所做的编译器优化(这不是由C编译器完成的),这会导致这种行为吗?

优化很可能是将读取缓存在寄存器中或完全消除代码.此优化通常需要大约10-30毫秒,因此您正在测试在程序完成之前是否已发生此优化.

还有一些额外的编译器必须做的事情,即使硬件已经支持它,也能获得Cache一致性吗?(如启用/禁用)

您必须正确使用该模型,忘记编译器将优化您的代码的想法,并理想地使用并发库来传递线程之间的工作.

public static void main(String... args) {
    final AtomicBoolean flag = new AtomicBoolean(true);
    /*
    * Thread1: Sleeps for 30ms and then sets i to 1
    */
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(30);
                System.out.println("Thread1: flag=" + flag);
                flag.set(false);
            } catch (Exception e) {
                throw new RuntimeException("Thread1 Error");
            }
        }
    }).start();

    /*
    * Thread2: Loops until flag is false and then exits.
    */
    new Thread(new Runnable() {
        @Override
        public void run() {
            long j = 0;
            while (flag.get())
                j++;
            System.out.println("\nThread2: flag=" + flag + ", j=" + j);
        }
    }).start();
}
Run Code Online (Sandbox Code Playgroud)

版画

Thread1: flag=true

Thread2: flag=false, j=39661265
Run Code Online (Sandbox Code Playgroud)

我应该默认使用Volatile用于所有共享变量吗?

几乎从不.如果你只设置了一次,那么它会有效.但是,使用锁定通常更有用.