我的项目中有以下类结构 A类和 B类:
class A {
private String id;
private List<B> bs = new ArrayList<B>();
A(String id){
this.id = id;
}
public List<B> getBs(){
return bs;
}
public void setBs(List<B> bs){
this.bs=bs;
}
public void addBs(List<B> bs){
this.bs.addAll(bs);
}
public void addB(B b){
this.bs.add(b);
}
}
class B {
private String id;
private List<String> tags;
B(String id){
this.id = id;
}
public List<String> getTags(){
return tags;
}
public void setTags(List<String> tags){
this.tags = tags;
}
public void addTags(List<String> tags){
this.tags.addAll(tags);
}
public void addTag(String tag){
this.tags.add(tag);
}
}
我们还有一个缓存类:
class CacheService {
private static final ConcurrentHashMap<String, Object> CACHE = new ConcurrentHashMap<String, Object>();
public static Object get(String id){
return CACHE.get(id);
}
public static void put(String id, Object obj){
return CACHE.put(id, obj);
}
}
现在,使用唯一ID创建A类和B类的对象,并通过&lt; id,object&gt;放入此缓存中。组合。例如:
A a1 = new A("10");
CacheService.put("10", a1);
A a2 = new A("11");
CacheService.put("11", a2);
B b1 = new B("1");
CacheService.put("1", b1);
B b2 = new B("2");
CacheService.put("2", b2);
B b3 = new B("3");
CacheService.put("3", b3);
B b4 = new B("4");
CacheService.put("4", b4);
此外,我将B类对象放在List<B>
内部对象a1
和a2
中。 重要的是要注意,唯一的B对象只在任何A对象中放置一次:
a1.add(b1);
a1.add(b2);
a2.add(b3);
a2.add(b4);
这样我们就可以在CACHE中为A和B类提供多个对象。
场景:现在多个线程访问此CACHE,但其中一些线程最终获取A类对象,而其他B类对象则取决于用户指定的ID。这些主题实际上想要对这些对象进行读取或更新信息。
问题:我的要求是当一个线程访问了类A的对象(例如a1
)来更新它时,其他任何线程都不能读取< / strong>或更新 a1
以及添加到b1
的所有B类对象(本例中为b2
和List<B>
)内部对象a1
,直到我完成a1
上的所有更新。请告诉我如何在这种情况下获得锁定?
答案 0 :(得分:3)
要进行深度复制,我建议您在A和B的每个实例中都有一个Lock
,并让它们都使用lock()
和unlock()
方法实现一些接口,其中class {{ 1}}将获得自己的锁,以及A
的所有锁。然后,在使用之前锁定一个对象,并在完成后解锁。
编辑:所以你的行动方针是:
B's
:Lockable
和lock()
unlock()
代替Lockable
为A和B添加私有字段
私人决赛Lock lock = new ReentrantLock();
Object
只是为了在锁Lockable
将获取本地锁定实例,并迭代b列表并调用它们的lock()
方法。 lock()
unlock()
,然后在完成后调用lock()
。答案 1 :(得分:2)
synchronized
关键字可以帮助您解决此问题。您可以将整个方法声明为synchronized
,也可以使用synchronized()
块,这些块会锁定您定义的特定键对象。输入synchronized()
块时,另一个线程无法访问锁定该相同键对象的其他块,直到退出该块为止。
请参阅同步here的Java教程。
对于您的示例,您可以执行以下任一操作:
public synchronized void addB(B b) {
this.bs.add(b);
}
OR
...在你的班级中声明一个锁定对象......
private final Object LOCK = new Object();
...并使用synchronized()
块:
public void addB(B b) {
synchronized(LOCK) {
this.bs.add(b);
}
}
使用第二个优先于第一个的优点是您可以完全,明确地控制哪些代码段被锁定(而不仅仅是整个方法调用)。在处理并发时,为了提高效率,您希望尽可能少地进行同步,因此使用此方法可以仅在最低限度上执行同步。
此外,我确信有人会指出您不需要显式声明另一个锁对象,因为您可以使用this
关键字在当前对象上进行同步。但是,this other StackOverflow question and answer总结了我不建议这么做的原因。
答案 2 :(得分:1)
EDIT2:在问题被编辑后我打算完成我的答案,但我刚刚找到了课程java.util.concurrent.locks.ReentrantReadWriteLock,其中我认为完全符合需求用户sohanlal。
这个类提供了一对锁,一个用于读取操作,可以由多个元素同时拥有,并且本身不是阻塞的。第二个(写入部分)在获取时避免任何读取或写入操作。
解决方案如下:
代码。这尚未经过测试,但会了解应如何实施:
class A {
private String id;
private List<B> bs = new ArrayList<B>();
private ReentrantReadWriteLock lk = new ReentrantReadWriteLock();
A(String id){
this.id = id;
}
public List<B> getBs(){
lk.readLock().lock(); // acquire the read lock, since this operation does not affect A contents
return bs;
lk.readLock().unlock();
}
public void setBs(List<B> bs){
lk.writeLock().lock(); // acquire the write lock, which automatically avoids further reading/writing operations
this.bs=bs;
for( B elem : bs )
{
// internal B elements need a reference to the reading part of the lock
elem.setLock(lk.readLock());
}
lk.writeLock().unlock();
}
public void addBs(List<B> bs){
[...] // similar approach that in setBs
}
public void addB(B b){
[...] // similar approach that in setBs
}
public void deleteB( B elem ) // Or whatever notation you want
{
lk.writeLock().lock(); // acquire the write lock
B internalElem = bs.get(bs.indexOf(elem));
if( internalElem != null )
{
bs.remove(internalElem);
bs.unsetLock();
}
lk.writeLock().unlock();
}
}
class B {
private String id;
private List<String> tags;
private Lock lk;
B(String id){
this.id = id;
lk = null;
}
public void setLock(Lock l){ lk = l; } // put additional controls if you want
public void unsetLock()
{
lk = null;
}
private void lockInternal()
{
if(lk!=null){ lk.lock(); }
}
private void unlockInternal()
{
if(lk!=null){ lk.unlock(); }
}
public List<String> getTags(){
List<String> ref = null;
lockInternal();
[...] //internal operations
unlockInternal();
return ref;
}
public void setTags(List<String> tags){
[...] // similar approach that in getTags
}
public void addTags(List<String> tags){
[...] // similar approach that in getTags
}
public void addTag(String tag){
[...] // similar approach that in getTags
}
}
原始答案:
杰夫的答案是一个很好的起点,因为它似乎解决了触摸A型物体并修改其构成的问题。但是,我们仍然遇到与A中包含的B型对象相关的问题。 假设我们有一个A型对象,里面有几个B型元素:
A a1 = new A("10");
B b1 = new B("myB1");
B b2 = new B("myB2");
B b3 = new B("myB3");
a1.add(b1);
a1.add(b2);
a1.add(b3);
问题是,如果您使用a1
进行操作,则需要锁定b1
,b2
和b3
上的任何操作。你怎么能确定这个?
我看到的唯一解决方案是A容器内的所有B元素共享一些常见的Lock变量。在a1
上调用与写入相关的操作时,它获取锁定,避免对B元素的任何操作。请注意,对B元素的操作不需要实际获取锁定...仅检查它是否已被A型容器获取。
许多缺点/考虑因素:
a1
中插入B对象时,必须为B提供共享锁。解压缩后,必须取消引用锁定。如果容器A被删除会怎么样?你必须照顾那种事情。编辑:我忘了提到这种机制,在编写A型容器时,不会“停止”或考虑对所包含的B型元素的持续操作。