Java 并发检查集合是否为空

Java 并发检查集合是否为空,java,multithreading,Java,Multithreading,我有一段代码: private ConcurrentLinkedQueue<Interval> intervals = new ConcurrentLinkedQueue(); @Override public void run(){ while(!intervals.isEmpty()){ //remove one interval //do calculations //add some intervals } } 私有ConcurrentLin

我有一段代码:

private ConcurrentLinkedQueue<Interval> intervals = new ConcurrentLinkedQueue();
@Override
public void run(){
  while(!intervals.isEmpty()){
    //remove one interval
    //do calculations
    //add some intervals
  }
}
私有ConcurrentLinkedQueue间隔=新建ConcurrentLinkedQueue();
@凌驾
公开募捐{
而(!interval.isEmpty()){
//删除一个间隔
//计算
//添加一些间隔
}
}
此代码同时由特定数量的线程执行。正如您所看到的,循环应该一直进行,直到集合中没有更多的间隔,但是出现了一个问题。在每次迭代开始时,从集合中删除一个间隔,最后可能会将一些间隔添加回同一集合中

问题是,当一个线程在循环中时,集合可能会变为空,因此尝试进入循环的其他线程将无法做到这一点,并且将提前完成其工作,即使在第一个线程完成迭代后集合中可能会充满值。我希望线程数保持不变(或不超过某个数字n),直到所有工作真正完成

这意味着当前没有线程在循环中工作,集合中也没有元素。实现这一目标的可能途径是什么?欢迎任何想法

在我的具体案例中,解决这个问题的一个方法是给每个线程一个不同的原始集合。但是在一个线程完成它的工作之后,程序就不再使用它了,即使它可以帮助其他线程进行计算,所以我不喜欢这个解决方案,因为在我的问题中利用机器的所有核心是很重要的

这是我能想到的最简单的最小工作示例。这可能太长了

public class Test{   
   private ConcurrentLinkedQueue<Interval> intervals = new ConcurrentLinkedQueue();
   private int threadNumber;
   private Thread[] threads;
   private double result;

   public Test(int threadNumber){
      intervals.add(new Interval(0, 1));
      this.threadNumber = threadNumber;
      threads = new Thread[threadNumber];
   }

   public double find(){
      for(int i = 0; i < threadNumber; i++){
         threads[i] = new Thread(new Finder());
         threads[i].start();
      }
      try{
         for(int i = 0; i < threadNumber; i++){
            threads[i].join();
         }
      }
      catch(InterruptedException e){
         System.err.println(e);
      }
      return result;
   }   

   private class Finder implements Runnable{   
      @Override
      public void run(){
         while(!intervals.isEmpty()){
            Interval interval = intervals.poll();
            if(interval.high - interval.low > 1e-6){    
               double middle = (interval.high + interval.low) / 2;
               boolean something = true;
               if(something){
                  intervals.add(new Interval(interval.low + 0.1, middle - 0.1));
                  intervals.add(new Interval(middle + 0.1, interval.high - 0.1));
               }
               else{
                  intervals.add(new Interval(interval.low + 0.1, interval.high - 0.1));
               }
            }
         }
      }
   }

   private class Interval{
      double low;
      double high;
      public Interval(double low, double high){
         this.low = low;
         this.high = high;
      }
   }
}
公共类测试{
私有ConcurrentLinkedQueue间隔=新ConcurrentLinkedQueue();
私有整数;
私有线程[]个线程;
私人双重结果;
公共测试(int threadNumber){
添加(新的间隔(0,1));
this.threadNumber=threadNumber;
螺纹=新螺纹[螺纹编号];
}
公共双查找(){
对于(int i=0;i1e-6){
双中=(区间高+区间低)/2;
布尔值=真;
如果(某物){
增加(新区间(区间低+0.1,中间-0.1));
增加(新区间(中间+0.1,区间高-0.1));
}
否则{
区间。添加(新区间(区间.低+0.1,区间.高-0.1));
}
}
}
}
}
私有类间隔{
双低;
双高;
公共间隔(双低、双高){
这个.低=低;
这个高=高;
}
}
}

您可能需要了解的是:在每次迭代之后,间隔应该消失(因为它太小)、变小或分成两个更小的间隔。在没有剩余时间间隔后,工作就完成了。此外,我应该能够限制执行此工作的线程数,使用一些数字n。实际程序通过划分区间并使用一些规则丢弃那些区间中不能包含最大值的部分来寻找某个函数的最大值,但这与我的问题无关。

我建议采用主/辅方法

主进程遍历间隔并将该间隔的计算分配给不同的进程。它还可以根据需要删除/添加。这样,所有的核心都被利用,并且只有当所有的间隔都完成时,这个过程才被完成。这也称为动态工作分配

一个可能的例子:

public void run(){
  while(!intervals.isEmpty()){
    //remove one interval
    Thread t = new Thread(new Runnable()
    {
        //do calculations
    });
    t.run();
    //add some intervals
  }
}

您提供的可能解决方案称为静态分配,您是正确的,它将以最慢的处理器的速度完成,但动态方法将利用所有内存。

您可以使用原子标志,即:

private ConcurrentLinkedQueue<Interval> intervals = new ConcurrentLinkedQueue<>();
private AtomicBoolean inUse = new AtomicBoolean();

@Override
public void run() {
    while (!intervals.isEmpty() && inUse.compareAndSet(false, true)) {
        // work
        inUse.set(false);
    }
}
私有ConcurrentLinkedQueue间隔=新建ConcurrentLinkedQueue();
私有AtomicBoolean inUse=新的AtomicBoolean();
@凌驾
公开募捐{
while(!interval.isEmpty()&&inUse.compareAndSet(false,true)){
//工作
inUse.set(假);
}
}
UPD

问题已经更新,所以我会给你们更好的解决方案。使用阻塞队列是更“经典”的解决方案

private BlockingQueue<Interval> intervals = new ArrayBlockingQueue<Object>();
private volatile boolean finished = false;

@Override
public void run() {
    try {
        while (!finished) {
            Interval next = intervals.take();
            // put work there
            // after you decide work is finished just set finished = true
            intervals.put(interval); // anyway, return interval to queue
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}
private BlockingQueue interval=new ArrayBlockingQueue();
private volatile boolean finished=false;
@凌驾
公开募捐{
试一试{
当(!完成){
Interval next=Interval.take();
//把工作放在那里
//确定工作已完成后,只需设置finished=true
interval.put(interval);//无论如何,将interval返回到队列
}
}捕捉(中断异常e){
Thread.currentThread().interrupt();
}
}
UPD2


现在似乎最好重新编写解决方案并将范围划分为每个线程的子范围。

您的问题看起来像是一个递归的问题-处理一个任务(间隔)可能会产生一些子任务(子间隔)

为此,我将使用
ForkJoinPool
RecursiveTask

class Interval {
    ...
}

class IntervalAction extends RecursiveAction {
    private Interval interval;

    private IntervalAction(Interval interval) {
        this.interval = interval;
    }

    @Override
    protected void compute() {
        if (...) {
            // we need two sub-tasks
            IntervalAction sub1 = new IntervalAction(new Interval(...));
            IntervalAction sub2 = new IntervalAction(new Interval(...));
            sub1.fork();
            sub2.fork();
            sub1.join();
            sub2.join();
        } else if (...) {
            // we need just one sub-task
            IntervalAction sub3 = new IntervalAction(new Interval(...));
            sub3.fork();
            sub3.join();
        } else {
            // current task doesn't need any sub-tasks, just return
        }
    }
}

public static void compute(Interval initial) {
    ForkJoinPool pool = new ForkJoinPool();
    pool.invoke(new IntervalAction(initial));
    // invoke will return when all the processing is completed
}

CompletableFuture类也是此类任务的有趣解决方案。 它会自动将工作负载分配到多个工作线程上

static CompletableFuture<Integer> fibonacci(int n) {
  if(n < 2) return CompletableFuture.completedFuture(n);
  else {
    return CompletableFuture.supplyAsync(() -> {
      System.out.println(Thread.currentThread());
      CompletableFuture<Integer> f1 = fibonacci(n - 1);
      CompletableFuture<Integer> f2 = fibonacci(n - 2);
      return f1.thenCombineAsync(f2, (a, b) -> a + b);
    }).thenComposeAsync(f -> f);
  }
}

public static void main(String[] args) throws Exception {
  int fib = fibonacci(10).get();
  System.out.println(fib);
}
静态Compl
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;

public class Test {
    final int numberOfThreads;
    final BlockingQueue<Integer> queue;
    final BlockingQueue<Integer> availableThreadsTokens;
    final BlockingQueue<Integer> sleepingThreadsTokens;
    final ThreadPoolExecutor executor;

    public static void main(String[] args) {
        final Test test = new Test(2); // arbitrary number of thread => 2
        test.launch();
    }

    private Test(int numberOfThreads){
        this.numberOfThreads = numberOfThreads;
        this.queue = new PriorityBlockingQueue<Integer>(); 
        this.availableThreadsTokens = new LinkedBlockingQueue<Integer>(numberOfThreads);
        this.sleepingThreadsTokens = new LinkedBlockingQueue<Integer>(numberOfThreads);
        this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(numberOfThreads);
    }

    public void launch() {

        // put some elements in queue at the beginning
        queue.add(1);
        queue.add(2);
        queue.add(3);

        for(int i = 0; i < numberOfThreads; i++){
            availableThreadsTokens.add(1);
        }

        System.out.println("Start");

        boolean algorithmIsFinished = false;

        while(!algorithmIsFinished){
            if(sleepingThreadsTokens.size() != numberOfThreads){
                try {
                    availableThreadsTokens.take();
                } catch (final InterruptedException e) {
                    e.printStackTrace();
                    // some treatment should be put there in case of failure
                    break;
                }
                if(!queue.isEmpty()){ // Continuation condition
                    sleepingThreadsTokens.drainTo(availableThreadsTokens);
                    executor.submit(new Loop(queue.poll(), queue, availableThreadsTokens));
                }
                else{
                    sleepingThreadsTokens.add(1);
                }
            }
            else{
                algorithmIsFinished = true;
            }
        }

        executor.shutdown();
        System.out.println("Finished");
    }

    public static class Loop implements Runnable{
        int element;
        final BlockingQueue<Integer> queue;
        final BlockingQueue<Integer> availableThreadsTokens;

        public Loop(Integer element, BlockingQueue<Integer> queue, BlockingQueue<Integer> availableThreadsTokens){
            this.element = element;
            this.queue = queue;
            this.availableThreadsTokens = availableThreadsTokens;
        }

        @Override
        public void run(){
            System.out.println("taking element "+element);
            for(Long l = (long) 0; l < 500000000L; l++){
            }
            for(Long l = (long) 0; l < 500000000L; l++){
            }
            for(Long l = (long) 0; l < 500000000L; l++){
            }
            if(element < 7){
                this.queue.add(element+1);
                System.out.println("Inserted element"+(element + 1));
            }
            else{
                System.out.println("no insertion");
            }
            this.availableThreadsTokens.offer(1);
        }
    }   
}