让一个线程从共享列表中获取多个对象

时间:2014-12-29 21:50:33

标签: java concurrency

我有一份按职业分类的可用员工名单(例如“程序员”,“测试员”), 每个可用职业的数量都存储在信号量中。 完成某项任务 - 每项任务都在不同的线程中完成 - 如果给出了专业列表(例如2“程序员”,1“经理”) 并且任务应该以全有或全无的方式获取它们 - 如果一切都可用,则取出列表中的所有内容,否则等待每个人都可用。

我已经通过使用BlockingQueue,Semaphore限制列表本身的访问权限或者只是手动锁定它来完成此操作。

我要问的是这样做的正确方法是什么,如果可能的话,如何让其他线程可以使用release方法。

3 个答案:

答案 0 :(得分:1)

您需要一台显示器(http://en.wikipedia.org/wiki/Monitor_%28synchronization%29)来完成您的任务。 它可以通过java.util.concurrent.Lock(ReentrantLock)和锁上的许多条件来实现。

答案 1 :(得分:1)

你的问题确实引起了我的兴趣。相当有趣的项目。这是一个基本的实现,似乎适用于您的描述。请参阅底部以获取可运行的示例。它相当有限(不支持负面获取,没有超时选项等),但它已经足够使用它,你可以根据需要轻松扩展它。

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Semaphore;

/** Represents a group of semaphores identified by distinct strings
 * Supports basic acquire and release operations. Other operations could be added as necessary
 * @author MPatashnik
 */
public class SemaphoreGroup {

    /** The total number of permits available to this, as it was constructed */
    private final HashMap<String, Integer> permits;

    /** The semaphores in this group, by their identifier */
    private final HashMap<String, Semaphore> semaphores;

    /** The semaphore monitoring use of operations in this SemaphoreGroup */
    private final Semaphore operationLock;

    /** A map of threads to permits they currently own */
    private final HashMap<Thread, Map<String, Integer>> threads;

    /** Set to true to see printing output of threads acquiring and releasing */
    private static final boolean DEBUG = false;

    /** Creates a SemaphoreGroup. All semaphores are initialized as unfair.
     * @param permits - the Number of permits for each identifier string
     */
    public SemaphoreGroup(Map<String, Integer> permits) {
        this.permits = new HashMap<String, Integer>(permits);
        operationLock = new Semaphore(1);
        semaphores = new HashMap<String, Semaphore>();
        threads = new HashMap<Thread, Map<String, Integer>>();
        for(String s : permits.keySet()){
            semaphores.put(s, new Semaphore(permits.get(s)));
        }
    }

    /** Attempts to acquire the given permits
     * @param permits                   - the permits to acquire
     * @throws InterruptedException     - see Semaphore.acquire()
     * @throws IllegalArgumentException - If one of the permits this wants to 
     *                                      acquire is an unrecognized string, or any of the
     *                                      permit acquisition counts is negative
     */
    public void acquire(Map<String, Integer> permits) 
            throws InterruptedException, IllegalArgumentException{
        try{
            operationLock.acquire();
            if(DEBUG) System.out.println("Acquired " + Thread.currentThread().getName());
            for(Map.Entry<String, Integer> e : permits.entrySet()){
                Semaphore s = semaphores.get(e.getKey());
                if(s == null){
                    throw new IllegalArgumentException("Illegal Permit Name " + e.getKey() + " Not in " + this);
                }
                if(e.getValue() < 0)
                    throw new IllegalArgumentException("Illegal Permit Value " + e.getValue() + " Must be positive");
                if(s.availablePermits() < e.getValue()){
                    operationLock.release();
                    if(DEBUG) System.out.println("Released " + Thread.currentThread().getName());
                    //Not enough permits - wait on semaphore until someone releases, then try again
                    synchronized(operationLock){
                        operationLock.wait();
                    }
                    acquire(permits);
                    return;
                }
            }
            //All semaphores ok. Do acquiring and exit
            for(Map.Entry<String, Integer> e : permits.entrySet()){
                semaphores.get(e.getKey()).acquire(e.getValue());
            }
            Thread t = Thread.currentThread();
            //Update information of this thread owning permits
            Map<String, Integer> currentlyOwned = threads.get(t);
            if(currentlyOwned == null){
                threads.put(t, new HashMap<String, Integer>(permits));
            }
            else{
                HashMap<String, Integer> totalOwned = new HashMap<String, Integer>(permits);
                for(Map.Entry<String, Integer> e : permits.entrySet()){
                    totalOwned.put(e.getKey(), 
                            e.getValue() 
                            + (totalOwned.get(e.getKey()) == null ? 0 : currentlyOwned.get(e.getKey())));
                }
                threads.put(t, totalOwned);
            }
        }
        finally{
            operationLock.release();            
            if(DEBUG) System.out.println("Released " + Thread.currentThread().getName());
        }
    }

    /** Attempts to release the given amounts of the given permits.
     * Won't release more permits for any identifier than this currently owns.
     * @param permits               - the permits to release.
     * @throws InterruptedException - see Semaphore.acquire
     */
    public void release(Map<String, Integer> permits) throws InterruptedException{
        try{
            operationLock.acquire();
            if(DEBUG) System.out.println("Acquired " + Thread.currentThread().getName());
            Thread t = Thread.currentThread();

            //Check to see if this thread has any permits at all
            if(! threads.containsKey(t))
                return;

            for(Map.Entry<String, Integer> e : permits.entrySet()){
                Semaphore s = semaphores.get(e.getKey());
                if(s == null){
                    throw new IllegalArgumentException("Illegal Permit Name " + e.getKey() + " Not in " + this);
                }
                int has = threads.get(t).containsKey(e.getKey()) ? threads.get(t).get(e.getKey()) : 0;
                int toRemove = Math.min(e.getValue(), has);
                s.release(toRemove);
                threads.get(t).put(e.getKey(), has - toRemove);
            }
            if(DEBUG){
                System.out.println("\nReleasing " + t);
                System.out.println(threads.toString().replaceAll("},", "}\n"));
            }


            //Ok, notify a thread wanting to acquire
            synchronized(operationLock){
                operationLock.notify();
            }
        }finally{
            operationLock.release();
            if(DEBUG) System.out.println("Released " + Thread.currentThread().getName());
        }
    }

    /** Releases all permits this currently owns for all identifiers within this Semaphore Group
     * @throws InterruptedException - see Semaphore.acquire
     */
    public void releaseAll() throws InterruptedException{
        try{
            operationLock.acquire();
            if(DEBUG) System.out.println("Acquired " + Thread.currentThread().getName());
            Thread t = Thread.currentThread();
            if(! threads.containsKey(t)) return;
            HashMap<String, Integer> permits = new HashMap<String, Integer>(threads.get(t));
            operationLock.release();
            if(DEBUG) System.out.println("Released " + Thread.currentThread().getName());
            release(permits);
        }finally{
            operationLock.release();
            if(DEBUG) System.out.println("Released " + Thread.currentThread().getName());
        }

    }

    /** Returns the permits (by identifier) this SemaphoreGroup still has available. */
    public Map<String, Integer> getAvailablePermits(){
        HashMap<String, Integer> available = new HashMap<>();
        for(Entry<String, Semaphore> e : semaphores.entrySet()){
            available.put(e.getKey(), e.getValue().availablePermits());
        }
        return available;
    }

    /** Returns the set of valid identifying strings for this semaphore group */
    public Set<String> getIdentifyingStrings(){
        return semaphores.keySet();
    }

    /** Returns the available permits out of the total as the toString */
    @Override
    public String toString(){
        Map<String, Integer> available = getAvailablePermits();
        String s = "{";
        for(Entry<String, Integer> e : permits.entrySet()){
            s += e.getKey() + "=" + available.get(e.getKey()) + "/" + e.getValue() + ", ";
        }
        return s.substring(0, s.length() - 2) + "}";
    }
}

Runnable随播:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.HashMap;
import java.util.LinkedList;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class ThreadRunner extends JFrame {

    private static LinkedList<Worker> threads;
    private static SemaphoreGroup semaphore;
    private static HashMap<String, Integer> totalPermits;

    public ThreadRunner(){
        setLayout(new BorderLayout());
        add(new InfoPanel(), BorderLayout.CENTER);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack();
        repaint();
        setVisible(true);
    }

    static class InfoPanel extends JPanel{

        public InfoPanel(){
            setPreferredSize(new Dimension(600, 500));
        }

        @Override
        public void paintComponent(Graphics g){
            Graphics2D g2d = (Graphics2D) g;
            g2d.setFont(new Font("Arial", Font.PLAIN, 15));
            int x = 20;
            int y = 20;
            g2d.drawString("Available: " + semaphore.toString(), x, y);
            y += 50;
            for(Worker t : threads){
                if(t.working) g2d.setColor(Color.RED);
                else    g2d.setColor(Color.BLACK);
                g2d.drawString(t.getName() + "-" + t.status + " : " + t.job.toString(), x, y);
                y += 25;
                if(! t.working) g2d.drawString("Next: " + t.nextJob.toString(), x + 150, y);
                y += 35;
            }
        }
    }

    static class Worker extends Thread{

        private volatile String status;
        private boolean working;
        private HashMap<String, Integer> job = new HashMap<>();
        private HashMap<String, Integer> nextJob = new HashMap<>();
        private int jobIndex;
        private static final int WORK_TIME = 2000;


        public Worker(int i){
            super("Worker " + i);
            jobIndex = 1;
        }

        @Override
        public void run(){
            try{
                createNextJob();
                while(true){
                    createNextJob();
                    HashMap<String, Integer> aJob = nextJob;
                    semaphore.acquire(aJob);
                    job = aJob;
                    working = true;

                    for(int i = 0; i < 10; i++){
                        Thread.sleep(WORK_TIME / 10);
                        status = ((i + 1) * 10) + "% done of Job " + jobIndex;
                    }

                    semaphore.releaseAll();
                    working = false;
                    job.clear();

                    jobIndex++;
                }
            } catch (InterruptedException e) {}
        }

        private void createNextJob(){
            nextJob = new HashMap<>();
            nextJob.put("Bronze", (int)(totalPermits.get("Bronze") * Math.random()));
            nextJob.put("Silver", (int)(totalPermits.get("Silver") * Math.pow(Math.random(), 2)));
            nextJob.put("Gold", (int)(totalPermits.get("Gold") * Math.pow(Math.random(), 3)));
            nextJob.put("Platinum", (int)(totalPermits.get("Platinum") * Math.pow(Math.random(), 4)));
        }

        @Override
        public String toString(){
            return getName();
        }

    }

    public static void main(String[] args){
        totalPermits = new HashMap<>();
        totalPermits.put("Bronze", 15);
        totalPermits.put("Silver", 10);
        totalPermits.put("Gold", 5);
        totalPermits.put("Platinum", 2);

        semaphore = new SemaphoreGroup(totalPermits);
        threads = new LinkedList<Worker>();

        final int NUMB_WORKERS = 5;
        for(int i = 0; i < NUMB_WORKERS; i++){
            threads.add(new Worker(i));
        }

        ThreadRunner tr = new ThreadRunner();

        //Start worker threads
        for(Worker w : threads){
            w.start();
        }

        //Monitor gui in main thread
        while(true){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            tr.repaint();
        }
    }

}

答案 2 :(得分:1)

这是一个完整的,有效的,人为的例子,我相信它符合概述的要求。

它跟踪信号量中可用资源的总数,BlockingQueues中的实际资源以及BlockingQueues中的任务。

如果它无法立即获取任务所需的资源,它会将任务重新提交到队列的后面(这可以通过其他方式完成,但在此示例中,它使用的是有界的工作线程池,因此您不会'我们一定希望他们等到资源可用,因为这可能会阻止可能立即运行的其他任务的并行化。)

package so.thread.resources;

import java.util.Date;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public class MultiResourcesMain {

  public static int numManagers = 5;
  public static int numProgrammers = 15;
  public static int numTesters = 5;

  public static Semaphore managersLease = new Semaphore(numManagers);
  public static Semaphore programmersLease = new Semaphore(numProgrammers);
  public static Semaphore testersLease = new Semaphore(numTesters);

  public static BlockingQueue<Manager> managers = new LinkedBlockingQueue<Manager>();
  public static BlockingQueue<Programmer> programmers = new LinkedBlockingQueue<Programmer>();
  public static BlockingQueue<Tester> testers = new LinkedBlockingQueue<Tester>();

  public static Random rand = new Random();

  public static BlockingQueue<Task> tasks = new LinkedBlockingQueue<>();

  public static Object resourceLock = new Object();

  public static AtomicBoolean running = new AtomicBoolean(true);

  public static AtomicInteger tasksRun = new AtomicInteger(0);
  public static AtomicInteger resubmits = new AtomicInteger(0);

  public static void main(String[] args) throws Exception {

    // prime the resources
    for (int i = 0; i < numManagers; i++) {
      managers.add(new Manager());
    }
    for (int i = 0; i < numProgrammers; i++) {
      programmers.add(new Programmer());
    }
    for (int i = 0; i < numTesters; i++) {
      testers.add(new Tester());
    }

    int numTasks = 100;
    int managersRandLimit = numManagers + 1;
    int programmersRandLimit = numProgrammers + 1;
    int testersRandLimit = numTesters + 1;

    // generate tasks to execute with random resource requirements
    for (int i = 0; i < numTasks; i++) {
      tasks.add(new Task(UUID.randomUUID().toString(), new TaskResources(rand.nextInt(managersRandLimit), rand.nextInt(programmersRandLimit), rand.nextInt(testersRandLimit))));
    }

    // spin up worker threads
    int numWorkers = 10;
    ExecutorService taskExecutor = Executors.newFixedThreadPool(numWorkers);
    for (int i = 0; i < numWorkers; i++) {
      taskExecutor.submit(new Worker());
    }

    while (tasksRun.get() < numTasks) {
      Thread.sleep(10);
    }

    running.set(false);

    taskExecutor.shutdown();
    taskExecutor.awaitTermination(2, TimeUnit.SECONDS);

    System.out.println(String.format("Done, ran %d tasks and resubmitted %d tasks due to insufficient resources at acquire time", tasksRun.get(), resubmits.get()));


  }

  public static class Worker implements Runnable {

    @Override
    public void run() {
      while (running.get()) {
        try {
          Task task = tasks.poll(1, TimeUnit.SECONDS);
          if (null != task) {
            if (acquireResources(task.resources)) {
              runTask(task);
              releaseResources(task.resources);
            } else {
              // couldn't execute task now, returning to task queue
              System.out.println(String.format("[%s :: %s] !!! Couldn't acquire resources for Task %s, resubmitting",
                Thread.currentThread().getName(), new Date(), task.id));
              tasks.add(task);
              resubmits.getAndIncrement();
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
      System.out.println(String.format("[%s :: %s] >>> Thread shutdown",
        Thread.currentThread().getName(), new Date()));
    }
  }

  public static void runTask(Task task) {
    Date now = new Date();
    long elapsed = now.getTime() - task.created.getTime();
    System.out.println(String.format("[%s :: %s] *** Running task with %d managers, %d programmers & %d testers, waited %d millis to execute for id %s",
      Thread.currentThread().getName(), now, task.resources.managers, task.resources.programmers, task.resources.testers, elapsed, task.id));
    tasksRun.getAndIncrement();
  }

  public static void releaseResources(TaskResources res) {
    synchronized (resourceLock) {
      managersLease.release(res.managers);
      programmersLease.release(res.programmers);
      testersLease.release(res.testers);
    }
  }

  public static boolean acquireResources(TaskResources res) {
    synchronized (resourceLock) {

      boolean acquiredManagers = false;
      boolean acquiredProgrammers = false;
      boolean acquiredTesters = false;

      acquiredManagers = managersLease.tryAcquire(res.managers);
      if (acquiredManagers) {
        acquiredProgrammers = programmersLease.tryAcquire(res.programmers);
        if (acquiredProgrammers) {
          acquiredTesters = testersLease.tryAcquire(res.testers);
        }
      }

      if (acquiredManagers && acquiredProgrammers && acquiredTesters) {
        return true;
      } else {
        // return unused resources
        if (acquiredProgrammers) {
          programmersLease.release(res.programmers);
        }
        if (acquiredManagers) {
          managersLease.release(res.managers);
        }
        return false;
      }
    }
  }

  public abstract static class Person {

  }

  public static class Manager extends Person {

  }

  public static class Programmer extends Person {

  }

  public static class Tester extends Person {

  }

  public static class Task {
    public String id;
    public TaskResources resources;
    public Date created = new Date();

    public Task(String id, TaskResources resources) {
      this.id = id;
      this.resources = resources;
    }
  }

  public static class TaskResources {
    public int managers;
    public int programmers;
    public int testers;

    public TaskResources(int managers, int programmers, int testers) {
      this.managers = managers;
      this.programmers = programmers;
      this.testers = testers;
    }
  }
}