我有一个java多线程问题。我有两个线程访问一个方法A(),它在内部有一个for循环,在循环中调用一个方法B()。应使用线程名锁定锁定方法A,并且应将方法B锁定在方法B操作的对象ID上。检查以下代码。
Currrent代码
private static final ConcurrentHashMap<Object, Object> LOCKS = new ConcurrentHashMap<Object, Object>();
private void methodA(){
LOCKS.putIfAbsent(Thread.currentThread().getName(), new Object()))
synchronized (LOCKS.putIfAbsent(Thread.currentThread().getName(), new Object())) {
for(loop through all objects) {
methodB(Object1);
}
}
}
private void methodB(Object1 object1) {
LOCKS.putIfAbsent(object1.getObjectId(), new Object()))
synchronized(LOCKS.putIfAbsent(object1.getObjectId(), new Object())){
//<Work on object1>
}
}
我已经完成了上面的代码,以确保2个不同的线程应该能够并行访问methodA(),但是在methodB()中一次不能在同一个Object1上工作(由methodA()调用)。 即;虽然我希望线程A和线程B同时访问methodA(),然后循环遍历'for'循环中的所有对象并通过调用methodB()对每个对象进行操作,我不希望线程A和B到一次作用于SAME对象实例。因此上面的代码基于对象实例ID锁定methodB()。
需要改进。
在上面的代码中,如果线程A和线程B来到methodB()并发现它们都想要在同一个对象'obj1'上工作,那么现在使用上面的代码,线程A将等待,或者线程B将等待对于另一个完成,取决于谁到达并首先锁定methodB()。
但想象一下,线程A首先获取锁定并执行methodB()需要9个小时才能完成处理'obj1'。在这种情况下,线程B需要等待整整9个小时才有机会执行methodB(),从而处理'obj1'。
我不希望这种情况发生。线程B,一旦发现方法B()被线程A锁定在'obj1'的名称中,就应该继续(稍后再回到obj1)来尝试锁定和处理其他对象。即;它应该尝试处理'for'循环中的其他对象,如对象列表中的obj1,obj2等。
任何能够解决这个“无需等待锁定”问题的输入都将受到赞赏。
非常感谢您的任何帮助。
一些澄清以改善答案。
答案 0 :(得分:5)
你能做的最好的事情就是保持简单。
应使用线程名锁
锁定方法A.
只有锁定共享对象才有意义。锁定线程本地锁是没有意义的。
synchronized(LOCKS.putIfAbsent(object1.getObjectId(),new Object()))
这将返回null
并在第一次运行时抛出NullPointerException。
我会用
替换代码private void methodA(){
List<Object1> objects = new ArrayList<>(this.objectList);
while(true) {
for(Iterator<Object1> iter = objects.iterator() : objects)
if(object1.methodB())
iter.remove();
if(objects.isEmpty()) break;
Thread.sleep(WAIT_TIME_BEFORE_TRYING_AGAIN);
}
}
// in class for Object1
final Lock lock = new ReentrantLock();
public boolean methodB() {
if (!lock.tryLock())
return false;
try {
// work on this
return true;
} finally {
lock.unlock();
}
}
根据您想要处理无法锁定的对象的方式,您可以将它们添加到后台ExecutorService。您可以让methodA重复调用失败的所有剩余对象。
理想情况下,您会找到一种方法来最小化锁定时间,甚至完全不需要锁定。例如像AtomicReference和CopyOnWriteArrayList这样的类是线程安全且无锁的。
答案 1 :(得分:1)
我不是一个Java人,但在我看来,你不会通过同步实现这一点。我相信你需要自己锁定。
如果您创建了java.util.concurrent.locks.ReentrantLock,则可以使用tryLock进入锁定(如果尚未锁定)。 methodA需要知道哪个methodB调用成功或哪个被取消,因为无法进行锁定。因此,您可以在methodA中执行锁定处理,从而为您提供完全控制。或者您可以在methodB中执行锁定,但是如果methodB完成其工作或者没有获得锁定,则需要一些返回值或异常处理来向signalA发送信号。
当然,您还需要在您已经完成的对象的方法A或您仍需要处理的对象中保留一个列表。
答案 2 :(得分:0)
如果一个线程偶尔传递'错过'传递中的对象,这是否重要?如果不是:
将所有对象存储在可锁定的队列样式容器中。让线程A,B等弹出一个,调用它上面的方法然后再将其推回。然后线程不可能同时对同一个对象进行操作。然后唯一的锁是在容器push / pop上,并且没有线程需要在任何延长的时间内阻塞。
..或类似的东西。我总是试图避免复杂的锁定方案 - 它们似乎总是搞砸了:(
答案 3 :(得分:0)
我建议采用不同的方法。不是直接调用该方法,而是将command object放入队列并让线程(或执行程序)处理命令。
当队列中出现命令时,尝试获取锁定。如果这不起作用,请将命令添加到队列的末尾。这将确保最终再次尝试该命令。
缺点:如果某个线程在每次尝试执行时碰巧锁定它,那么命令可以无限期推迟。
这里的解决方案是确保您只锁定您需要的东西。这样,当你看到“哦,这是锁定的”时,你知道有人已经在完成任务,你可以忘记命令( - &gt;不做两次工作)。