2017-06-04 179 views
3

我知道这意味着如果你声明一个数组volatile,那么对数组的引用不是数组中的项。挥发性阵列的奇怪行为

我学习互斥算法,所以我写了一些测试代码:

public class MutualExclusion { 
    static final int N = 10; 
    static final int M = 100000; 

    volatile static int count = 0; 

    public static void main(String[] args) { 
     Thread[] threads = new Thread[N]; 
     for (int i = 0; i < N; i++) { 
      Thread t = new Worker(i); 
      threads[i] = t; 
      t.start(); 
     } 
     for (Thread t: threads) { 
      try { 
       t.join(); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
     if (count != N * M) { 
      System.out.println("count (" + count + ") != N * M (" + String.valueOf(N * M) + ")"); 
     } 
    } 

    static class Worker extends Thread { 
     int id; 
     Worker(int id) { 
      this.id = id; 
     } 

     @Override 
     public void run() { 
      for (int i = 0; i < M; i++) { 
       this.lock(); 
       // critical section 
       count++; 
       if (i % 1000 == 0) { 
        System.out.println(this.getName() + ": " + count); 
       } 
       this.unlock(); 
      } 
     } 


     void lock() { 
      filterLock(); 
     } 

     void unlock() { 
      filterUnlock(); 
     } 

     static volatile int level[] = new int[N]; 
     static volatile int lastToEnter[] = new int[N - 1]; 

     void filterLock() { 
      for (int i = 0; i < (N - 1); i++) { 
       level[this.id] = i; 
       lastToEnter[i] = this.id; 
       outer: 
       while (lastToEnter[i] == this.id) { 
        for (int k = 0; k < N; k++) { 
         if (k != this.id && level[k] >= i) { 
          continue outer; 
         } 
        } 
        break; 
       } 
      } 
     } 

     void filterUnlock() { 
      level[this.id] = -1; 
     } 
    } 
} 

在我的第一个实施滤波算法,我错过了volatile可变levellastToEnter,这并不奇怪,程序进入一个无限循环。在我添加了缺失的volatile后,该程序可以按预期结束。

正如我在开始时所说的,volatile数组并不意味着数组中的项是易失性的,那么为什么在添加缺少的volatile后程序结束了预期?

当我添加volatile关键字后,我在执行另一个互斥算法时仍然无法正确运行,我问自己这个问题。我有一个小窍门(Java volatile array?),使项目在阵列看起来是挥发性的:(下面的代码可以被粘贴到直接Worker类)

volatile static boolean[] b = new boolean[N]; 
volatile static boolean[] c = new boolean[N]; 
volatile static int k = 0; 

void dijkstraLock() { 
    b[this.id] = false; 
    outer: 
    for (;;) { 
     if (k == this.id) { 
      c[this.id] = false; 

      c = c; // IMPORTANT! the trick 

      for (int i = 0; i < N; i++) { 
       if (i != this.id && !c[i]) { 
        continue outer; 
       } 
      } 
      break; 
     } else { 
      c[this.id] = true; 
      if (b[k]) { 
       k = this.id; 
      } 
     } 
    } 
} 

void dijkstraUnlock() { 
    b[this.id] = true; 
    c[this.id] = true; 
} 
+0

你认为你可以幸运吗?仅仅因为您的代码一次正确运行,或者即使它在您的特定机器上始终正常运行,也并不意味着它始终能够正常运行。与Dijkstra锁定代码的一个区别是,您正在写入主循环中的volatile int'count',这会在每次循环迭代时在线程之间创建一个瞬间发生 - 之前的关系(请注意,您正在更新'count' ,因为'count ++'不是原子的) –

+0

@ErwinBolwidt我也认为代码正确运行可能是幸运的,但是,我多次运行代码以确认这一点。如果没有volatile,filterLock会在执行后很快进入死循环,这与volatile关键字的情况非常不同。我打算使用'count ++',因为互斥算法(如果实现正确)可以确保只有一个线程正在执行关键部分代码。 – Yon

回答

1

挥发性阵列在Java中不含有挥发性元素 - 但如果你通过数组引用(这是易失性的)访问它们,你将得到一个易失性读取。例如,在上面的代码:

static volatile int lastToEnter[] = new int[N - 1]; 

是易失性的写入,而

lastToEnter[i] = this.id; 

不是。然而,阵列值的评价 - 如:

lastToEnter[i] == this.id 

挥发性读 - 先读出的基准,其是易失性的阵列,并且只有然后访问第i个元素,以评估其值。

我怀疑这是你的执行成功的原因,一旦数组被声明为volatile。

+2

为什么volatile读取如此特别? OP发布的dijkstra锁也使用volatile读取,但没有'c = c;'技巧,它不起作用。一个易失性的读取本身在Java内存模型中不起作用 - 如果易失性读取由另一个线程执行易失性*写入*,而不是数组本身**,则它仅在两个线程之间建立一个“发生之前”关系,而不是到它里面的一个元素。 –

+0

我明白你的观点。您建议一系列易失性读取 - 因为线程在整个代码中以易失性读取访问数组 - 不强制线程有任何关系吗? – Assafs

+1

不符合Java内存模型。问题是JVM的实际实现可能不那么严格,因此即使根据JMM未正确同步,某些代码也可能正确运行。但随后出现JVM的下一个版本,或者Intel提供了一个新的JVM使用的并行化新功能,然后突然使用的代码可能会开始失败。 –