繁体   English   中英

Java多线程-无需等待即可锁定

[英]Java multithreading - locking without waiting

我有一个Java多线程问题。 我有2个线程访问methodA(),该方法内部有一个for循环,并且在循环中调用methodB()。 应该使用线程名锁来锁定方法A,并且应该将方法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()中的同一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等。

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

非常感谢您的任何帮助。

一些澄清,以改善答案。

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

您可以做的最好的事情就是保持简单。

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

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

已同步(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之类的类是线程安全且无锁的。

我不是Java专家,但我认为您不会通过同步实现这一目标。 我相信您将需要自己进行锁定。

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

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

一个线程传递偶尔会在传递中“丢失”一个对象是否重要? 如果不:

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

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

我建议使用另一种方法。 与其直接调用该方法,不如将命令对象放入队列中,并让线程(或执行程序)处理命令。

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

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

解决方案是确保仅锁定所需的内容。 这样,当您看到“哦,这已被锁定”时,您就知道有人已经在执行该任务,您可以简单地忘记该命令(->不要重复执行两次)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM