简体   繁体   English

Java多线程-从列表中删除项目

[英]Java Multithreading - Remove items from list

new to multithreading here please bear with me. 多线程的新功能在这里请多多包涵。 I'm trying to run 2 threads which remove items from a list of tasks (10 tasks in total) until the taskList is empty. 我正在尝试运行2个线程,这些线程将从任务列表(总共10个任务)中删除项目,直到taskList为空。

What i have so far is: 到目前为止,我有:

Main method: 主要方法:

public static void main(String[] args) {


        List<Task> taskList = new ArrayList<Task>();
        List<Thread> threadList = new ArrayList<Thread>();


        for (int i = 1; i <= 10; i++) {

            taskList.add(new Task("some details");
        }

        TaskManager manager = new TaskManager();
        gestor.setTaskList(taskList);

        Thread t1 = new Thread(taskManager);            
        Thread t2 = new Thread(taskManager);    

        threadList.add(t1);
        threadList.add(t2);

        if(threadList.size() > 0){
            for (Thread thread : threadList){                               
                thread.start();             
            }
        }   

        for (Thread thread : threadList){
            try {
                thread.join();
            } catch (InterruptedException e) {
                System.out.println("thread " + Thread.currentThread().getName() + " was interrupted");
            }
        }

        System.out.println("END OF MAIN");

    }

Task Manager class: 任务管理器类:

public class TaskManager implements Runnable {

    private List<Task> availableTasks;
    private Random random = new Random();

    public void setAvailableTasks(List<Task> availableTasks) {
    this.availableTasks = availableTasks;
}

    @Override
    public void run() {
        while (!availableTasks.isEmpty()) {
            takeTask();
        }
    }

    public void takeTask() {
        try {
            Thread.sleep(1000);
            int index = random.nextInt(availableTasks.size());
            Task task = availableTasks.get(index);

            printDetails(task);
            availableTasks.remove(task);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void printDetails(Task task) {
        //here it should print the details to the console  
    }
}

The thing is, it either runs 2 times or it's always the same thread running 10 times. 事实是,它要么运行2次,要么总是同一线程运行10次。 I know it's probably a silly question but I hope someone can clarify it! 我知道这可能是一个愚蠢的问题,但我希望有人能澄清一下! Thanks in advance 提前致谢

Edit: I was able to make it work using @Lidae's suggestion 编辑:我能够使用@Lidae的建议使其工作

Take task method edited like so: 像这样编辑任务方法:

public void takeTask() {
        try {
            Thread.sleep(1000);
            synchronized (this) {
                if (!availableTasks.isEmpty()) {
                    int index = random.nextInt(availableTasks.size());
                    Task task = availableTasks.get(index);

                    printDetails(task);
                    availableTasks.remove(task);
                }
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

Your code has some concurrency problems as a result of several threads trying to access the same object at the same time. 由于多个线程试图同时访问同一对象,因此您的代码存在一些并发问题。 For example one thing that can happen is that thread A gets a task from the list (in Task task = availableTasks.get(index) ), then there is a context switch and that very task is removed by thread B, and by the time thread A tries to remove the task, it is already gone (this wouldn't cause an exception in your code, but it could be bad anyway depending on what exactly you plan on doing with the task). 例如,可能发生的事情是线程A从列表中获取任务(在Task task = availableTasks.get(index) ),然后进行了上下文切换,并且线程B随即删除了该任务。线程A尝试删除该任务,但该任务已经消失了(这不会在代码中引起异常,但是无论如何,这可能会很糟糕,这取决于您打算对该任务执行的确切计划)。

You also can't be sure that the list is not empty when you try to get a task from it: it was empty when it last checked in the while-loop, but between then and the time that it attempts to take a task, another thread might have already taken the last task. 当您尝试从列表中获取任务时,您也不能确定列表是否为空:上一次在while循环中但在此之后到尝试执行任务之间,该列表为空。另一个线程可能已经执行了最后一个任务。 This is true even if you remove the call to Thread.sleep. 即使您删除对Thread.sleep的调用,也是如此。

What you need to is make sure that the availableTasks list is only modified by one thread at a time. 您需要确保一次只能由一个线程修改availableTasks列表。 This can be done in various ways, for example by using a Semaphore, or by using synchronized methods in the shared data object. 这可以通过多种方式来完成,例如,通过使用信号量,或通过在共享数据对象中使用同步方法。

it's always the same thread running 10 times 始终是同一线程运行10次

My guess is because your list is too small, so first thread runs and finishes the job before second thread have a chance to start working. 我的猜测是因为您的列表太小,所以第一个线程可以运行并完成工作,然后第二个线程才有机会开始工作。 Make task list longer, like 1000+ tasks, maybe even more. 使任务列表更长,例如1000多个任务,甚至更多。

it either runs 2 times 它要么运行两次

this is probably because your task list is not thread safe, make it thread safe using Collections.SynchronizedList 这可能是因为您的任务列表不是线程安全的,请使用Collections.SynchronizedList使其成为线程安全的

    for (int i = 1; i <= 10; i++) {

        taskList.add(new Task("some details");
    }

    taskList = Collections.synchronizedList(taskList);

    TaskManager manager = new TaskManager();

Can't reproduce this. 无法重现此内容。

I've corrected (quite a few) compilation problems with your code and ran it, getting: 我已经用您的代码纠正(不少)编译问题并运行它,得到:

Thread-1 printing some details for task 5
Thread-0 printing some details for task 8
Thread-0 printing some details for task 2
Thread-1 printing some details for task 7
Thread-1 printing some details for task 1
Thread-0 printing some details for task 3
Thread-0 printing some details for task 6
Thread-1 printing some details for task 9
Thread-0 printing some details for task 4
Thread-1 printing some details for task 0

So both threads run and process tasks. 因此,两个线程都运行并处理任务。

One thing which is important is that the access to the list of tasks should be synchronized. 重要的一件事是应该同步对任务列表的访问。 And not just Collections.synchronizedList , you have at least four places where you access your list of tasks. 不仅是Collections.synchronizedList ,您还有至少四个位置可以访问任务列表。 This is why the execution of the program almost always ends with: 这就是为什么程序的执行几乎总是以以下结尾的原因:

java.lang.IllegalArgumentException: n must be positive
    at java.util.Random.nextInt(Random.java:250)
    at TaskManager.takeTask(TaskManager.java:25)
    at TaskManager.run(TaskManager.java:18)
    at java.lang.Thread.run(Thread.java:662)

Your TaskManager.run method first check for isEmpty and then gets a random task from the list. 您的TaskManager.run方法首先检查isEmpty ,然后从列表中获取随机任务。 Another thread may remove the last task of the list between these two operations. 另一个线程可能会删除这两个操作之间的列表的最后一个任务。 Resulting in random.nextInt(0) despite you've previously checked that the list is not empty. 尽管您先前已经检查过列表不为空, random.nextInt(0)导致random.nextInt(0)

Better would be something like: 更好的是这样的:

private Task nextTask() {
    synchronize(availableTask) {
        if (availableTask.isEmpty()) {
            return null;
        } else {
            return availableTasks.get(random.nextInt(availableTasks.size()));
        }
    }
}

Adding to what @Lidae answered. 除了@Lidae回答的内容。 It is standard multiple-producer to multiple-consumer problem. 它是标准的多生产者到多消费者的问题。 There are couple of articles on the same.. 在同一篇文章上有几篇文章。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM