在两个线程之间共享一个 ArrayList?
Posted
技术标签:
【中文标题】在两个线程之间共享一个 ArrayList?【英文标题】:Sharing an ArrayList between two threads? 【发布时间】:2017-03-12 13:33:20 【问题描述】:所以我有两个线程正在运行,其中一个应该从用户那里获取信息,另一个线程应该使用用户提供的信息,如下所示:
public class UserRequest implements Runnable
@Override
public void run()
// TODO Auto-generated method stub
String request;
Scanner input = new Scanner(System.in);
while(true)
System.out.println("Please enter request:");
request = input.nextLine();
try
//do something
catch(IOException e)
e.printStackTrace();
第二个线程:
public class Poller implements Runnable
ArrayList<String> colors = new ArrayList<String>();
public void poll()
for(String color : colors)
if(color == "")
//do work
else
//do work
@Override
public void run()
colors.add("Violet");
colors.add("Green");
colors.add("Yellow");
colors.add("Orange");
while(true)
poll();
我想做的是获取用户在UserRequest
对象中输入的任何输入,然后将其推入Poller
对象中的ArrayList
,以便它也可以在新值上“工作”。我看过像BlockingQueue
这样的东西,但我不希望任何一个线程等待另一个线程,因为除了数据共享之外,他们还有其他任务需要完成。我该怎么做呢?
【问题讨论】:
ArrayList 不是线程安全的。 什么是线程安全的替代方案,我将如何在线程之间共享它? 正如许多人所指出的,队列可能就是您要寻找的。您可以将 ConcurrentLinkedQueue 用于线程安全而无需过度锁定的东西。 【参考方案1】:由于您使用了动词“push”和“poll”,看来您正在寻找Queue
而不是List
。
因此,我认为您正在寻找ConcurrentLinkedQueue
,记录在案的here。
它允许您让您的 UserRequest
对象提供它,并让您的 Poller
对象使用它。
虽然看起来你的Poller
对象会有相当高的 CPU 消耗,因为打开的while
没有任何wait
:
public class Poller implements Runnable
Queue<String> colors = new ConcurrentLinkedQueue<String>();
public void poll()
while(this.colors.isEmpty())
Thread.currentThread().wait();
String color = this.colors.poll();
while(color != null)
if(color == "")
//do work
else
//do work
color = this.colors.poll();
@Override
public void run()
colors.offer("Violet");
colors.offer("Green");
colors.offer("Yellow");
colors.offer("Orange");
while(true)
this.poll();
此代码需要进行一些更改才能运行,但它包含了您需要的几乎所有内容。
它的作用非常简单:它不断轮询,直到没有剩余元素为止。
一旦发生这种情况,Poller
对象会要求当前的Thread
休眠,因为如果没有Queue
中的元素,它就没有必要运行。
public class UserRequest implements Runnable
@Override
public void run()
String request;
Scanner input = new Scanner(System.in);
while(true)
System.out.println("Please enter request:");
request = input.nextLine();
try
//do something
catch(IOException e)
e.printStackTrace();
finally
this.notifyAll(); // Notifies all sleeping threads to wake up
如果你注意到了,我只是在你的 UserRequest
类中添加了一个 notifyAll
调用。为什么?非常简单:notifyAll
唤醒所有 wait
ing Thread
s,这正是所有没有元素的 Poller
s 正在做的事情。
一旦调用,Poller
s 将唤醒,检查其颜色 Queue
是否具有元素并与它们一起使用。如果Queue
没有元素,它们将再次休眠,直到UserRequest
再次唤醒它们,依此类推。
【讨论】:
【参考方案2】:有两种方法可以解决这个问题:
1) 它使用thread safe collection
,如ConccurentLinkedQueue
用于生产者-消费者、工作消耗等逻辑。如果您想使用实现List interface
的类(因此,您可以采用相同的方法平时ArrayList
),一定要看CopyOnWriteArrayList
那边,但是注意这个类使用阻塞同步。
2)另一种方法是使用内置的java同步工具,例如
Semaphore CyclicBarrier CountDownLatch Locks Phaser 通常的wait/notify
机制
有关更多详细信息,您必须阅读规范。让我们考虑一个使用 Semaphore 的例子:
private final Semaphore semaphore = new Semaphore(2, true);
public void appendToList() throws InterruptedException
available.acquire();
arrayList.add(.....); //put here what u need
public void putItem(Object x)
if (someLogicHere(x)) //semaphore releases counter in this place
available.release();
当然,您可以结合使用所有这些,例如你可以同时使用几个semaphores
,或者使用diff工具。
【讨论】:
【参考方案3】:“但我不希望任何一个线程等待另一个线程,因为除了这种数据共享之外,它们还有其他任务需要完成。”
没有办法做到这一点。任何适当的类线程总是会遇到这样一个问题,即您需要让一个线程等待而另一个线程执行某些操作。关键是你想最小化它。您只想使线程非常短暂且很少停止,并且仅在不这样做会导致它出现故障的情况下。您可以使用其中一种同步数据结构,也可以自己编写一点同步代码。
唯一有问题的对象是数组列表,并且您希望任一线程上的绝对最小停顿量。因此,您可能希望根据 arraylist 本身的对象对其进行同步。因此,只需在访问 arraylist 对象的点周围编写几个小同步块。
public class Poller implements Runnable
ArrayList<String> colors;
public Poller(ArrayList<String> colors)
this.colors = colors;
//pass in colors object, if modified from the scanner side it must synchronize the block around the colors object too.
public void doWork(String color)
//do work
public void addColor(String color)
synchronized (colors)
colors.add(color);
@Override
public void run()
while (!Thread.interrupted())
if (!colors.isEmpty())
String color;
synchronized (colors)
color = colors.remove(0);
doWork(color); //work done outside synch
try
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
关键是永远不要同时删除或添加东西到列表中。您不能将列表作为一个整体进行循环,因为如果工作是在循环中完成的,则会出现问题,并且数组的大小可能会发生变化,因此您不知道它是多少位。但是,您可以为此使用 ArrayList,只需同步更改数据结构的代码块并从该同步块中取出字符串,然后对其进行处理。这样,only 停顿是一个线程正在读取或写入而另一个线程需要的短暂瞬间。两者都是非常快速的操作。
【讨论】:
【参考方案4】:如果你想访问用户从 poller 对象输入的新值,那么:
由于对象存储在堆中,而不是在 Poller 类中创建 arrayList 的 新实例,您只需从 UserRequest 发送列表对象的引用。这样当您更改时添加新值到 userRequest 中的 arrayList 它将反映在 Poller 正在使用的 arrayList 中。例如,您可以这样做:
public class UserRequest implements Runnable
private ArrayList<String> arrayList = new ArrayList<String>();
@Override
public void run()
// TODO Auto-generated method stub
String request;
Scanner input = new Scanner(System.in);
while(true)
System.out.println("Please enter request:");
request = input.nextLine();
try
Poller poller = new Poller(arrayList);
Thread t = new Thread(poller);
t.start();
catch(IOException e)
e.printStackTrace();
您可以像这样更改 Poller 类:
public class Poller implements Runnable
private ArrayList arrayList = null;
Poller(ArrayList<String> arrayList)
this.arrayList = arrayList;
public void poll()
for(String color : arrayList)
if(color == "")
//do work
else
//do work
@Override
public void run()
while(true)
poll();
但是,您应该向 arrayList 添加一个侦听器,而不是在无限循环中调用 pool,以便仅在将新值添加到 List 时调用 poll()
。
您可以查看此链接以了解有关将侦听器添加到 ArrayList
的更多信息:https://***.com/a/16529462/7083385
【讨论】:
在多线程环境中使用ArrayList
s会导致ConcurrentModificationException
被抛出,因为不能保证读写顺序。
是的,但@Jenna 想用 ArrayList 来做
OP 还提供了 BlockingQueue
选项,它修复了并发性,但会阻止用户输入,直到 Poller
使用它。
在循环中尝试删除arrayList
的对象时,您也可以获得ConcurrentModificationException
如果OP想在使用arrayList中做到这一点,我认为没有其他方法【参考方案5】:
您可以使用队列。队列有自己的 poll 方法。您可以将其设为静态,但我怀疑这是最好的方法。一般来说,我使用 spring 在某种包装类中实例化队列,但看起来你并没有走那条路。
【讨论】:
我同意将Queue
设为静态并不是最好的方法。 OP 也没有说任何关于 spring
的内容,即使如此,注入也不能解决问题,因为你仍然需要知道你在注入什么。以上是关于在两个线程之间共享一个 ArrayList?的主要内容,如果未能解决你的问题,请参考以下文章