使用两个线程向Vector(或ArrayList)写入/读取

ero*_*ras 5 java concurrency multithreading

我有两个线程,它们都访问Vector.t1添加一个随机数,而t2删除并打印第一个数字.下面是代码和输出.t2似乎只执行一次(在t1开始之前)并永远终止.我在这里错过了什么吗?(PS:同样使用ArrayList测试)

import java.util.Random;
import java.util.Vector;

public class Main {

public static Vector<Integer> list1 = new Vector<Integer>();

public static void main(String[] args) throws InterruptedException {
    System.out.println("Main started!");

    Thread t1 = new Thread(new Runnable() {

        @Override
        public void run() {
            System.out.println("writer started! "); 
            Random rand = new Random();

            for(int i=0; i<10; i++) {
                int x = rand.nextInt(100);
                list1.add(x);
                System.out.println("writer: " + x);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }               
        }

    });

    Thread t2 = new Thread(new Runnable() {

        @Override
        public void run() {
            System.out.println("reader started! ");         
            while(!list1.isEmpty()) {

                int x = list1.remove(0);
                System.out.println("reader: "+x);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }               
        }

    });


    t2.start();
    t1.start();

    t1.join();
    t2.join();      
}
Run Code Online (Sandbox Code Playgroud)

输出:主要开始了!读者开始了!作家开始了!作者:40位作家:9位作家:23位作家:5位作家:41位作家:29位作家:72位作家:73位作家:95位作家:46位

Edw*_*uck 4

这听起来像是一个理解并发的玩具,所以我之前没有提到它,但我现在会提到(在顶部,因为它很重要)。

如果这是生产代码,请不要自行编写。java.util.concurrent 中有大量实现良好(已调试)的并发数据结构。使用它们。


消费时,您不需要根据“消耗的所有物品”关闭您的消费者。这是由于竞争条件造成的,即消费者可能会“领先”于生产者并仅因为生产者尚未写入要消费的项目而检测到空列表。

有多种方法可以完成消费者的关闭,但没有一种方法可以通过单独查看要使用的数据来完成。

我的建议是,当生产者完成生产时,生产者向消费者发出“信号”。然后,当消费者收到“信号”、不再产生数据并且列表为空时,它将停止。

替代技术包括创建“关闭”项。“生产者”添加关闭项,消费者只有看到“关闭”项才关闭。如果您有一组消费者,请记住您不应删除关闭项(否则只有一个消费者会关闭)。

此外,消费者可以“监视”生产者,这样,如果生产者“活动/存在”并且列表为空,则消费者假设将有更多数据可用。当生产者死亡/不存在并且没有可用数据时,就会发生关闭。

您使用哪种技术取决于您喜欢的方法以及您想要解决的问题。


我知道人们喜欢优雅的解决方案,但如果您的单一生产者意识到单一消费者,第一个选择看起来像。

public class Producer {

   public void shutdown() {
      addRemainingItems();
      consumer.shutdown();
   }
}
Run Code Online (Sandbox Code Playgroud)

消费者看起来像{

public class Consumer {

   private boolean shuttingDown = false;

   public void shutdown() {
     shuttingDown = true;
   }

   public void run() {
     if (!list.isEmpty() && !shuttingDown) {
        // pull item and process
     }
   }
}
Run Code Online (Sandbox Code Playgroud)

请注意,缺乏对列表中项目的锁定本质上是危险的,但您仅声明了一个消费者,因此不存在从列表中读取的争用。

现在,如果您有多个使用者,则需要提供保护以确保单个项目不会同时被两个线程拉取(并且需要以所有线程关闭的方式进行通信)。