Java 同步线程安全对象列表中的多个读取和写入
假设我有一个线程安全类容器:Java 同步线程安全对象列表中的多个读取和写入,java,multithreading,concurrency,synchronization,thread-safety,Java,Multithreading,Concurrency,Synchronization,Thread Safety,假设我有一个线程安全类容器: public class Container { private List<MyObject> objects; ... public synchronized MyObject take(Type t) {...} //takes an object of type t public synchronized void put(MyObject o) {...} //put an object } 我想在每个容器上循环以获取正确类
public class Container {
private List<MyObject> objects;
...
public synchronized MyObject take(Type t) {...} //takes an object of type t
public synchronized void put(MyObject o) {...} //put an object
}
我想在每个容器上循环以获取正确类型的对象
然后我有其他线程执行类似的操作:
//This piece of code is not synchronized
for(Container c : containers) {
Type t = getRandomType();
MyObject o = c.getObject(t); //note that this is synchronized
if(o != null) { //if I found an object of the desired type
//do some important stuff
return;
}
}
waitingReaders.putMyself(); //wait for the object of the right type
//This piece of code is not synchronized
Container c = getRandomContainer(); //from containers
Type t = getRandomType();
MyObject o = new MyObject(t); //creates an object of a random type
if(waitingReaders.containSomeoneWaitingForThisType(t)) {
waitingReaders.givesObjectToHim(o);
return;
}
else
c.put(o); //note that this is synchronized
编辑:如果读卡器没有找到所需的t类型对象,他会在结构waitingReaders中等待它(他的实现并不重要)。如果一个编写器发现一个读卡器正在等待生成的对象,那么他会将它交给等待的读卡器(而不是放在容器中)
问题在于,在读线程已经分析for循环中的某个容器之后,writer线程可能会将类型为t的对象放入某个容器中。因此,如果对象的类型是正确的t,那么读取器将错过该对象(它在循环中已经太超前了)
同时,我不想锁定for循环,因为我还需要保持并发读取
在这些约束条件下,您将如何处理所描述的场景?如果我理解正确,您需要一种解决方案,其中多个线程可以存储
MyObject
实例,多个线程可以检索特定类型的MyObject
实例,所有非阻塞,唯一的例外是当请求类型的实例不可用时,检索线程可能会被阻塞,直到请求类型的新实例可用为止
不要试图自己实施存储解决方案。使用现有的并发工具:
final ConcurrentHashMap<Type, BlockingQueue<MyObject>> map=new ConcurrentHashMap<>();
/**
* Get a object of {@code Type}, blocking if necessary
*/
MyObject getObjectOf(Type t) throws InterruptedException {
return map.computeIfAbsent(t, x->new LinkedBlockingQueue<>()).take();
}
/**
* Store an object, never blocking.
*/
void putObject(MyObject o) {
map.computeIfAbsent(o.getType(), x->new LinkedBlockingQueue<>()).add(o);
}
这使用了一个无界的
LinkedBlockingQueue
perType
,因此线程被阻塞的唯一情况是当线程试图从空队列检索项目时。您必须确保线程能够看到其他线程所做的更改。您可以使用同步或volatile变量或java.util.concurrent提供的锁,或者使用适合并发使用的集合,如CopyOnWriteArrayList。。。在并发更新的集合上循环时根本不进行任何同步可能会也可能不会工作,这取决于各种参数,并可能导致代码的不可预测行为。如果另一个线程在循环后放置新对象,是否会产生差异?如果没有,那么接受读卡器可能会错过循环之间的对象。结果是一样的。@AndréR.:问题明确指出getObject
是synchronized
是的,这会产生不同,因为我简化了一个更大的问题。如果读者没有找到对象,他就开始等待,如果他在等待,作者会将对象交给读者(而不是放在容器中)。也许我应该加上这个。我加了更多的细节。关键是作者的任务是通知读者。
final ConcurrentHashMap<Type, BlockingQueue<MyObject>> map=new ConcurrentHashMap<>();
/**
* Get a object of {@code Type}, blocking if necessary
*/
MyObject getObjectOf(Type t) throws InterruptedException {
return map.computeIfAbsent(t, x->new LinkedBlockingQueue<>()).take();
}
/**
* Store an object, never blocking.
*/
void putObject(MyObject o) {
map.computeIfAbsent(o.getType(), x->new LinkedBlockingQueue<>()).add(o);
}
final ConcurrentHashMap<Type, BlockingQueue<MyObject>> map=new ConcurrentHashMap<>();
/**
* Get a object of {@code Type}, blocking if necessary
*/
MyObject getObjectOf(Type t) throws InterruptedException {
return getQueue(t).take();
}
/**
* Store an object, never blocking.
*/
void putObject(MyObject o) {
getQueue(o.getType()).add(o);
}
private BlockingQueue<MyObject> getQueue(Type key) {
BlockingQueue<MyObject> q=map.get(key);
if(q!=null) return q;
BlockingQueue<MyObject> newQueue=new LinkedBlockingQueue<>();
q=map.putIfAbsent(key, newQueue);
return q==null? newQueue: q;
}