【问题标题】:Shuffling array in multiple threads在多个线程中洗牌数组
【发布时间】:2011-06-11 18:42:18
【问题描述】:

我有一个大小为 N 的数组。我想在 2 个线程(或更多线程)中随机播放它的元素。每个线程都应该使用它自己的数组部分。

比方说,第一个线程将元素从 0 打乱到 K,第二个线程将元素从 K 打乱到 N(其中 0

//try-catch stuff is ommited
static void shuffle(int[] array) {
   Thread t1 = new ShufflingThread(array, 0, array.length / 2);
   Thread t2 = new ShufflingThread(array, array.length / 2, array.length);
   t1.start();
   t2.start();
   t1.join();
   t2.join();
}

public static void main(String[] args) {
   int array = generateBigSortedArray();
   shuffle(array);
}

JVM 是否有任何保证,在这样的洗牌后,我会看到 main 方法中的 array 发生变化?

我应该如何实现ShufflingThread(或者,我应该如何运行它,可能在synchronized 块内或其他任何地方)以获得这样的保证?

【问题讨论】:

  • 我想任何线程级缓存在join()返回后都会结束,所以最后你必须在array中获得一致的数据而不声明它volatile。但是对于共享数组,我并不完全确定。也许复制数组的任何一半并将其传递给其中一个线程是个好主意。
  • 只是为了您的兴趣,这个任务非常适合 Java 7 Fork/join API,gee.cs.oswego.edu/dl/jsr166/dist/jsr166ydocs
  • 你会遇到的问题是前半部分的元素永远不会出现在后半部分,反之亦然。因此,您正在改组两个独立的数组(恰好出现在同一个对象中),即您没有改组整个数组。如果你这样洗牌,你可能每次都先得到所有的黑牌,然后得到所有的红牌。
  • @Peter Lawrey:谢谢,这是一个有用的观点。

标签: java multithreading concurrency jvm


【解决方案1】:

join() 调用足以确保内存一致性:当t1.join() 返回时,主线程“看到”线程t1 对数组所做的任何操作。

此外,Java 保证数组不会出现字撕裂:不同的线程可以使用同一数组的不同元素,而无需同步。

【讨论】:

    【解决方案2】:

    我认为这是线程控制方面的一个很好的练习,其中 (1) 一个作业可以分解为几个部分 (2) 这些部分可以独立和异步运行 (3) 主线程监视所有这些的完成各自线程中的作业。您所需要的只是让这个主线程等待()并被通知()-ed jobCount 次,每次线程完成执行。这是您可以编译/运行的示例代码。取消注释 println() 以查看更多信息。

    注意:[1]JVM不保证线程的执行顺序[2]你的主线程访问大数组时需要同步,以免数据损坏......

    公共类 ShufflingArray {

    private int nPart = 4,      // Count of jobs distributed, resource dependent
            activeThreadCount,  // Currently active, monitored with notify
            iRay[];         // Array the threads will work on
    
        public ShufflingArray (int[] a) {
        iRay = a;
        printArray (a);
    }
    
    private void printArray (int[] ia) {
        for (int i = 0 ; i < ia.length ; i++)
            System.out.print (" " + ((ia[i] < 10) ? " " : "") + ia[i]);
        System.out.println();
        }
    
    public void shuffle () {
        int startNext = 0, pLen = iRay.length / nPart;  // make a bunch of parts
        for (int i = 0 ; i < nPart ; i++, activeThreadCount++) {
            int start = (i == 0) ? 0 : startNext,
                stop = start + pLen;
            startNext = stop;
            if (i == (nPart-1))
                stop = iRay.length;
            new Thread (new ShuffleOnePart (start, stop, (i+1))).start();
        }
        waitOnShufflers (0);        // returns when activeThreadCount == 0
        printArray (iRay);
    }
    
    synchronized private void waitOnShufflers (int bump) {
        if (bump == 0) {
            while (activeThreadCount > 0) {
                // System.out.println ("Waiting on " + activeThreadCount + " threads");
                try {
                    wait();
                } catch (InterruptedException intex) {
        }}} else {
            activeThreadCount += bump;
            notify();
    }}
    
    public class ShuffleOnePart implements Runnable {
        private int startIndex, stopIndex;      // Operate on global array iRay
    
        public ShuffleOnePart (int i, int j, int k) {
            startIndex = i;
            stopIndex = j;
            // System.out.println ("Shuffler part #" + k);
        }
    
        // Suppose shuffling means interchanging the first and last pairs
        public void run () {
            int tmp = iRay[startIndex+1];
            iRay[startIndex+1] = iRay[startIndex];  iRay[startIndex] = tmp;
            tmp = iRay[stopIndex-1];
            iRay[stopIndex-1] = iRay[stopIndex-2];  iRay[stopIndex-2] = tmp;
            try {   // Lets imagine it needs to do something else too
                Thread.sleep (157);
            } catch (InterruptedException iex) { }
            waitOnShufflers (-1);
    }}
    
    public static void main (String[] args) {
        int n = 25, ia[] = new int[n];
        for (int i = 0 ; i < n ; i++)
            ia[i] = i+1;
        new ShufflingArray(ia).shuffle();
    

    }}

    【讨论】:

      【解决方案3】:

      Thread.start() 和 Thread.join() 足以为您提供数组初始化之间的 happens-before 关系,将其移交给线程,然后在主要方法。

      导致happens-before的操作是documented here

      正如在其他地方提到的,ForkJoin 非常适合这种分而治之的算法,并且可以让您从其他需要实施的大量簿记中解放出来。

      【讨论】:

        【解决方案4】:

        使用 java.util.Concurrent 包中的 ExecutorService 和 Callable Task 从每个运行的线程返回数组的一部分,一旦两个线程都完成,这是实现一致行为的另一种方法。

        【讨论】:

          【解决方案5】:

          好吧,它们不能同时访问同一个数组,如果你使用锁、互斥锁或任何其他同步机制,你就会失去线程的力量(因为要么必须等待另一个,要么完成洗牌或完成一点洗牌)。 你为什么不把数组分成两半,给每个线程它的数组位,然后合并两个数组呢?

          【讨论】:

            猜你喜欢
            • 2015-12-11
            • 1970-01-01
            • 1970-01-01
            • 2015-04-15
            • 1970-01-01
            • 2012-03-13
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多