1

我有一个java多线程问题。我有 2 个线程访问一个内部有一个 for 循环的 methodA(),并且在循环中调用一个 methodB()。方法 A 应使用线程名锁锁定,方法 B 应锁定在方法 B 操作的对象 id 上。检查下面的代码。

当前代码

        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()(由 methodA() 调用)中一次在同一个 Object1 上工作。IE; 虽然我希望线程 A 和线程 B 同时访问 methodA(),这反过来将遍历“for”循环中的所有对象,并通过调用 methodB() 对每个对象进行操作,但我不希望线程 A 和 B一次作用于相同的对象实例。因此,上面的代码根据对象实例 ID 锁定 methodB()。

需要的改进。

在上面的代码中,如果线程 A 和线程 B 来到 methodB() 并发现它们都想处理同一个对象 'obj1',现在使用上面的代码线程 A 将等待或线程 B 将等待另一个一个完成取决于谁首先到达并锁定了 methodB()。

但想象一个情况,线程 A 先获得锁并执行 methodB() 需要 9 个小时才能完成处理“obj1”。在这种情况下,线程 B 需要等待整整 9 个小时,然后才有机会执行 methodB() 并因此处理“obj1”。

我不希望这种情况发生。线程 B,一旦发现 methodB() 被线程 A 锁定在 'obj1' 的名称中,就应该继续(稍后再回到 obj1)尝试锁定和处理其他对象。IE; 它应该尝试处理“for”循环中的其他对象,例如对象列表中的 obj1、obj2 等。

任何解决此“无需等待即可锁定”问题的输入将不胜感激。

非常感谢您的帮助。

一些澄清以改进答案。

  1. methodA() 和 methodB() 都在同一个类中。methodB() 不在 Object 类中。
  2. 实际上线程 A 和线程 B 是调用包括 A 和 B 在内的许多方法的计时器线程。因此线程级锁(因为线程每 15 分钟左右调用一次,并且有可能之前不会完成 methodA() 的第一次执行第二次调用它)。
  3. methodB(Obj1) 始终采用 Object1 参数并且必须锁定它。原因是,在这个类中还有其他方法,比如 methodC(Obj1) 和 methodD(Obj1),它们也接受 Object1 参数。对于 Object1 的同一实例,这些方法不应同时执行。因此需要锁定 Object1 参数。
  4. 发现methodB(Obj1 obj) 已经被obj1 上的线程A() 锁定的线程B 需要以某种方式再次调用methodB() 但使用不同的对象,比如obj2。一旦与其他人一起完成,它应该回到 obj1 。
4

4 回答 4

5

你能做的最好的事情就是保持简单。

方法 A 应该使用线程名锁来锁定

只有锁定共享对象才有意义。锁一个线程本地锁是没有意义的。

同步(LOCKS.putIfAbsent(object1.getObjectId(),新对象()))

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 这样的类是线程安全和无锁的。

于 2012-08-09T11:31:07.083 回答
1

我不是 Java 人,但在我看来,您不会通过同步来实现这一点。我相信您将需要自己进行锁定。

如果你创建了一个 java.util.concurrent.locks.ReentrantLock 你可以使用 tryLock 进入锁,如果它还没有被锁定。methodA 需要知道哪个 methodB 调用是成功的,或者哪个被取消了,因为锁是不可能的。因此,您可以在 methodA 中进行锁定处理,让您在那里完全控制。或者您可以在方法 B 中进行锁定,但是如果方法 B 完成工作或没有获得锁定,您需要一些返回值或异常处理来向方法 A 发出信号。

当然,您还需要在 methodA 中保留您已经经历过的对象或您仍需要处理的对象的列表。

于 2012-08-09T11:59:36.710 回答
0

如果一个线程偶尔通过传递时“错过”一个对象,这有关系吗?如果不:

将所有对象存储在可锁定的队列式容器中。让线程 A、B 等弹出一个,在其上调用方法,然后将其推回。线程不可能同时对同一个对象进行操作。唯一的锁是容器推送/弹出,没有线程需要阻塞任何延长的时间。

..或类似的东西。我总是尽量避免复杂的锁定方案——它们似乎总是搞砸:(

于 2012-08-09T13:18:34.517 回答
0

我建议采用不同的方法。不是直接调用该方法,而是将命令对象放入队列中并让线程(或执行程序)处理命令。

当队列中出现命令时,尝试获取锁。如果这不起作用,请将命令添加到队列的末尾。这将确保最终再次尝试该命令。

缺点:如果某个线程在每次尝试执行时都碰巧锁定了命令,则该命令可能会被无限期推迟。

这里的解决方案是确保你只锁定你需要的东西。这样,当您看到“哦,这是锁定的”时,您就知道有人已经在处理该任务,您可以简单地忘记该命令(-> 不要工作两次)。

于 2012-08-09T13:20:19.517 回答