使用线程更改数组中数据的有效方法

时间:2014-03-06 16:07:30

标签: java arrays multithreading bytearray

我一直试图找出最有效的方法,许多线程在位级别上改变一个非常大的字节数组。为了便于解释,我将围绕一个多线程的Eratosthenes筛子提出问题,以便于解释这个问题。虽然我不会完全完成代码,因为我将省略某些与之无直接关系的部分。筛子也不会完全优化,因为这不是直接的问题。筛子将以这样的方式工作,即它保存哪个值是字节数组中的素数,其中每个字节包含7个数字(由于所有被签名的东西,我们不能改变第一位)。

让我们说我们的目标是找到低于1 000 000 000(10亿)的所有素数。因此,我们需要一个长度为1 000 000 000/7 +1或142 857 143(约1.43亿)的字节数组。

class Prime {
    int max = 1000000000;    
    byte[] b = new byte[(max/7)+1];

    Prime() {
        for(int i = 0; i < b.length; i++) {
            b[i] = (byte)127;  //Setting all values to 1 at start
        }
        findPrimes();
    }

    /*
     * Calling remove will set the bit value associated with the number
     * to 0 signaling that isn't an prime
     */
    void remove(int i) {
        int j = i/7; //gets which array index to access
        b[j] = (byte) (b[j] & ~(1 << (i%7)));
    }

    void findPrimes() {
        remove(1); //1 is not a prime and we wanna remove it from the start
        int prime = 2;
        while (prime*prime < max) {
            for(int i = prime*2; i < max; i = prime + i) {
                remove(i);
            }
            prime = nextPrime(prime); //This returns the next prime from the list
        }
    }

... //Omitting code, not relevant to question
}

现在我们得到了一个基本的大纲,其中某些内容贯穿某个多重复制表的所有数字,如果我们发现它们不是素数,则调用remove来删除符合数字的数字设置位。

现在我们创建线程来检查我们。我们拆分工作,以便每个人从表中删除一部分。因此,例如,如果我们有4个线程并且我们正在通过乘法表运行素数2,我们将在8次表中分配线程1全部,起始偏移量为2,即4,10,18,... 。,第二个线程的偏移量为4,因此它会经过6,14,22 ......等等。然后他们会调用他们想要的删除。

现在回答真正的问题。因为大多数人都可以看到,当素数小于7时,我们将有多个线程访问相同的数组索引。例如,在运行2时,我们将有线程1,线程2和线程3都将尝试访问b [0]以更改导致我们不想要的竞争条件的字节。

因此,问题是,优化对字节数组的访问的最佳方法是什么。

到目前为止,我对它的想法是:

  1. synchronized放在remove方法上。这显然很容易实现,但这是一个可怕的想法,因为它可以消除线程中的任何类型的收益。
  2. 创建一个与字节数组大小相等的互斥锁数组。要输入索引,需要在同一索引上使用互斥锁。这会相当快,但需要内存中另一个非常大的数组,这可能不是最好的方法
  3. 将存储在字节中的数字限制为我们开始运行的素数。因此,如果我们从2开始,我们将有每个数组的数字。然而,这将使我们的阵列长度增加到500,000 000(5亿)。
  4. 在没有过度使用内存的情况下,还有其他方法可以快速,最佳地完成此操作吗?

    (这是我在这里的第一个问题,所以我尽量详细而详尽,但我会接受任何关于如何改进问题的评论 - 更多细节,需要更多细节等。)

2 个答案:

答案 0 :(得分:5)

您可以使用原子整数数组。不幸的是,没有getAndAND,这对于您的remove()函数来说是理想的,但您可以循环使用CAS:

java.util.concurrent.atomic.AtomicIntegerArray aia;

....

void remove(int i) {
    int j = i/32; //gets which array index to access
    do {
        int oldVal = aia.get(j);
        int newVal = oldVal & ~(1 << (i%32));
        boolean updated = aia.weakCompareAndSet(j, oldVal, newVal);
    } while(!updated);
}

基本上你一直在尝试调整插槽以移除那个位,但只有在没有其他人从你下面修改它时你才能成功。安全,而且可能非常有效。 weakCompareAndSet基本上是一个抽象的Load-link/Store conditional指令。

顺便说一下,没有理由不使用符号位。

答案 1 :(得分:1)

我认为你可以避免同步线程...

例如,此任务:

for(int i = prime*2; i < max; i = prime + i) {
            remove(i);
}

它可以在小任务中进行分区。

for (int i =0; i < thread_poll; i++){
    int totalPos =  max/8; // dividing virtual array in bytes
    int partitionSize  = totalPos /thread_poll; // dividing bytes by thread poll
    removeAll(prime, partitionSize*i*8,  (i + 1)* partitionSize*8);
}
....

//没有比赛!!!

void removeAll(int prime, int initial; int max){
    k = initial / prime;
    if (k < 2) k = 2;
    for(int i = k * prime; i < max; i = i + prime) {
        remove(i);
    }
}