Java 如何从每个线程的数组中读取唯一元素?
我有一个基于数组的对象,它实现了以下接口:Java 如何从每个线程的数组中读取唯一元素?,java,multithreading,concurrency,scalability,Java,Multithreading,Concurrency,Scalability,我有一个基于数组的对象,它实现了以下接口: public interface PairSupplier<Q, E> { public int size(); public Pair<Q, E> get(int index); } 我的解决方案: 我创建原子整数索引,在每次下一次调用时递增 PairSupplier pairs; AtomicInteger index; public boolean hasNext(){ return t
public interface PairSupplier<Q, E> {
public int size();
public Pair<Q, E> get(int index);
}
我的解决方案:
我创建原子整数索引,在每次下一次调用时递增
PairSupplier pairs;
AtomicInteger index;
public boolean hasNext(){
return true;
}
public Pair<Q, E> next(){
int position = index.incrementAndGet() % pairs.size;
if (position < 0) {
position *= -1;
position = pairs.size - position;
}
return pairs.get(position);
}
PairSupplier对;
原子整数指数;
公共布尔hasNext(){
返回true;
}
公共对下一个(){
int position=index.incrementAndGet()%pairs.size;
如果(位置<0){
位置*=-1;
位置=对。尺寸-位置;
}
返回对。获取(位置);
}
对和索引在所有线程之间共享
我发现这个解决方案不可伸缩(因为所有线程都是增量的),也许有人有更好的想法
此迭代器将由50-1000个线程使用您有一条信息(“有人已经使用了这对
吗?”)必须在所有线程之间共享。所以一般情况下,你被卡住了。但是,如果您对数组的大小和线程的数量有所了解,那么可以使用bucket来减少痛苦
假设我们知道将有1000000个数组元素和1000个线程。为每个线程分配一个范围(线程#1获取元素0-999等)。现在,不再需要1000个线程争用一个AtomicInteger,您完全可以不争用
如果您可以确保所有线程都以大约相同的速度运行,那么这是可行的。如果需要处理线程#1有时忙于做其他事情而线程#2处于空闲状态的情况,可以稍微修改bucket模式:每个bucket都有一个原子整数。现在线程通常只会与自己抗争,但是如果它们的bucket是空的,那么它们可以转到下一个bucket。在您的示例中,有一个关键的事情是不清楚的——这到底是什么意思
元素的顺序并不重要,线程可以在不同的时间获取相同的元素
“不同的时间”意味着什么?相隔N毫秒?这是否意味着两条线绝对不会同时接触同一对线?我会这么想
如果要降低线程相互阻塞以争夺同一对的概率,并且存在对的备份数组,请尝试以下操作:
- 将数组划分为
numPairs/threadCount
子数组(您不必实际创建子数组,只需从不同的偏移量开始,但更容易将其视为子数组)
- 将每个线程分配给不同的子数组;当线程耗尽其子数组时,增加其子数组的索引
- 假设我们有6对和2个线程-您的作业看起来像线程1:[0,1,2]线程2:[3,4,5]。当线程1启动时,它将看到与线程2不同的一组对,因此它们不太可能争夺同一对
- 如果两个线程确实不能同时接触一对对象这一点很重要,那么在
synchronized(Pair)
(在实例上同步,而不是在类型上!)中包装所有与Pair对象接触的代码-可能偶尔会有阻塞,但您永远不会在一件事情上阻塞所有线程,与AtomicInteger
一样,线程只能相互阻止,因为它们实际上试图接触同一对象
请注意,不保证永远不会阻塞-因此,所有线程必须以完全相同的速度运行,处理每个Pair对象必须花费完全相同的时间,操作系统的线程调度程序将永远不会从一个线程而不是另一个线程窃取时间。你不能假设这些事情中的任何一件。通过划分要工作的区域,并将共享的最小状态单元设为锁,这样就有更高的可能性获得更好的并发性
但这是在数据结构上获得更多并发性的常用模式—在线程之间对数据进行分区,以便它们很少同时接触同一个锁。最容易看到的是创建哈希集或映射,并为每个线程提供唯一的哈希。在这之后,只需简单地通过这个散列代码即可。我在理解您试图解决的问题时遇到了一些困难
每个线程是否处理整个集合
是否担心没有两个线程可以同时在同一对上工作?但是每个线程需要处理集合中的每一对吗
还是希望使用所有线程一次性处理集合?这是标准的java信号量使用问题。下面的javadoc给出了与您的问题几乎相似的示例
如果您需要更多帮助,请告诉我?您的问题详细信息模棱两可-您的示例表明,两个线程可以被交给相同的对,但您在描述中没有这样说
由于更难实现,我将提供一个Iterable
,它将为每个线程提供一个Pair
s,直到供应商循环-然后它将重复
public interface Supplier<T> {
public int size();
public T get(int index);
}
public interface PairSupplier<Q, E> extends Supplier<Pair<Q, E>> {
}
public class IterableSupplier<T> implements Iterable<T> {
// The common supplier to use across all threads.
final Supplier<T> supplier;
// The atomic counter.
final AtomicInteger i = new AtomicInteger();
public IterableSupplier(Supplier<T> supplier) {
this.supplier = supplier;
}
@Override
public Iterator<T> iterator() {
/**
* You may create a NEW iterator for each thread while they all share supplier
* and Will therefore distribute each Pair between different threads.
*
* You may also share the same iterator across multiple threads.
*
* No two threads will get the same pair twice unless the sequence cycles.
*/
return new ThreadSafeIterator();
}
private class ThreadSafeIterator implements Iterator<T> {
@Override
public boolean hasNext() {
/**
* Always true.
*/
return true;
}
private int pickNext() {
// Just grab one atomically.
int pick = i.incrementAndGet();
// Reset to zero if it has exceeded - but no spin, let "just someone" manage it.
int actual = pick % supplier.size();
if (pick != actual) {
// So long as someone has a success before we overflow int we're good.
i.compareAndSet(pick, actual);
}
return actual;
}
@Override
public T next() {
return supplier.get(pickNext());
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported.");
}
}
}
公共接口供应商{
公共整数大小();
公共T-get(int-index);
}
公共接口对供应商扩展供应商{
}
公共类IterableSupplier实现Iterable{
//用于所有螺纹的通用供应商。
最终供应商;
//原子计数器。
最终AtomicInteger i=新的AtomicInteger();
公共IT供应商(供应商){
此项。供应商=供应商;
}
@凌驾
公共迭代器迭代器(){
/**
*您可以为每个线程创建一个新的迭代器,同时所有线程都共享一个迭代器
*因此,将在不同的线程之间分配每一对。
*
*您还可以跨多个线程共享同一迭代器。
*
*除非序列循环,否则任何两个线程都不会两次获得同一对。
*/
PairSupplier pairs;
AtomicInteger index;
public boolean hasNext(){
return true;
}
public Pair<Q, E> next(){
int position = index.incrementAndGet() % pairs.size;
if (position < 0) {
position *= -1;
position = pairs.size - position;
}
return pairs.get(position);
}
public interface Supplier<T> {
public int size();
public T get(int index);
}
public interface PairSupplier<Q, E> extends Supplier<Pair<Q, E>> {
}
public class IterableSupplier<T> implements Iterable<T> {
// The common supplier to use across all threads.
final Supplier<T> supplier;
// The atomic counter.
final AtomicInteger i = new AtomicInteger();
public IterableSupplier(Supplier<T> supplier) {
this.supplier = supplier;
}
@Override
public Iterator<T> iterator() {
/**
* You may create a NEW iterator for each thread while they all share supplier
* and Will therefore distribute each Pair between different threads.
*
* You may also share the same iterator across multiple threads.
*
* No two threads will get the same pair twice unless the sequence cycles.
*/
return new ThreadSafeIterator();
}
private class ThreadSafeIterator implements Iterator<T> {
@Override
public boolean hasNext() {
/**
* Always true.
*/
return true;
}
private int pickNext() {
// Just grab one atomically.
int pick = i.incrementAndGet();
// Reset to zero if it has exceeded - but no spin, let "just someone" manage it.
int actual = pick % supplier.size();
if (pick != actual) {
// So long as someone has a success before we overflow int we're good.
i.compareAndSet(pick, actual);
}
return actual;
}
@Override
public T next() {
return supplier.get(pickNext());
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported.");
}
}
}