【问题标题】:What is ReentrantLock#tryLock(long,TimeUnit) doing when it tries to aquire the lock?ReentrantLock#tryLock(long,TimeUnit) 在尝试获取锁时在做什么?
【发布时间】:2012-01-13 23:08:20
【问题描述】:

ReentrantLock#tryLock(long,TimeUnit) 实现在尝试获取锁时在做什么?假设线程A确实拥有myLock的锁,线程B调用myLock.tryLock(10,SECONDS),线程B是休眠还是等待?

换句话说,就是这两种实现的区别:

1.

while (true)
   try {
     if (readLock.tryLock())
       return;
     MILLISECONDS.sleep(5);
   }catch (InterruptedException e) {}

2.

 while (true)
   try {
     if (readLock.tryLock(5,MILLISECONDS))
       return;
   }catch (InterruptedException e) {}

【问题讨论】:

  • 在您的代码 (2) 中,您没有注册要在释放锁时通知的线程,而是尝试轮询它。总体而言,只需 readLock.lock() 代码会更好

标签: java multithreading concurrency java.util.concurrent


【解决方案1】:

有关如何实现锁和其他并发原语的重要参考,请参阅 Shavit 和 Herlihy 的出色 The Art of Multiprocessor Programming

【讨论】:

    【解决方案2】:

    从技术上讲,等待线程的状态没有区别。来自 JavaDoc:

    如果锁被另一个线程持有,则当前线程被禁用 用于线程调度目的并处于休眠状态 [...]

    这与睡眠时发生的情况非常相似,但我想除非我们知道实现,否则我们无法确定。

    现在,请注意这部分:

    [...] 处于休眠状态,直到发生以下三种情况之一: 锁被当前线程获取;或 [...]

    这意味着如果锁在此期间空闲,它将获取它并返回。另一种情况,线程在休眠时,即使空闲也没有机会获得锁。

    这两种情况之间可能出现的另一个细微差别是,定时 trylock 对 ReentrantLock 的公平策略很敏感。那就是:

    如果此锁已设置为使用公平排序策略,则可用锁 如果有其他线程在等待锁,则不会被获取。

    已知不定时的trylock是不公平的,即使其他线程已经在等待它也可能成功获取锁。

    【讨论】:

      【解决方案3】:

      我猜第二个将等待 5 毫秒来获得与第一个将立即尝试锁定不同的锁定。所以线程 B 将等待,如果在 5 毫秒(5 毫秒内)锁定它没有获得锁定它会返回 false。通常,如果您的超时时间为 5 毫秒,则没有区别,但如果您增加此数字,您将获得清晰的图像。

      5ms 是超时,它将等待 5ms 锁定,这意味着如果锁定在 3ms 后可用,它将在 3ms 后返回 true。

      【讨论】:

      • tryLock(long,TimeUnit) 在尝试获取锁之前不会等待,[JavaDoc]("docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/…, java.util.concurrent.TimeUnit)") 会说:“如果锁定可用此方法立即返回值为 true。"
      • @Chriss 这正是我的意思。我没有说它会在锁定之前等待,我很难过它会等待 5 毫秒才能获得它。
      【解决方案4】:

      正在等待锁,线程处于休眠状态。

      在内部,如果tryLock(long, TimeUnit) 方法未能立即获取锁,它会等待指定的时间。如果锁在此时间之前可用,它会立即返回锁。请注意,在这种情况下,当有多个线程请求锁定时,ReentrantLock 将随机选择一个线程将锁定交给下一个。可以通过将true 传递给构造函数new ReentrantLock(true) 中的公平值来更改此行为。

      第二个示例将仅每五毫秒检查一次锁。如果锁在休眠时可用,并且在唤醒之前被分配给另一个线程,则该线程将无法获取锁。

      如果您在使用此代码时有许多线程在等待锁定,请注意,您提供的任何解决方案都不能保证每个线程都会在某个时候获得锁定。在五毫秒结束之前,第二个代码可以继续被另一个线程狙击。第一个代码是随机的,但即使设置了公平值,每个线程也会每五毫秒放弃一次排队。如果是这种情况,您最好增加超时值。一个好的值应该是您期望每个线程获得一个轮次所花费的最大时间的两倍。

      【讨论】:

      • 注意在这种情况下,当有多个线程请求锁时,ReentrantLock 会随机选择一个线程将锁交给下一个。 这并不完全正确,即使在非公平版本中,它也会选择 waiting 线程队列中的第一个线程。但是,如果任何其他线程在它被释放期间(在解除等待服务员之前)尝试获取锁,它可以获取它 - 因此最后一个线程可以成为第一个。服务员一般被订单唤醒,然后进入等待队列。 Fair 版本要求锁的拥有者是第一个服务员。
      【解决方案5】:

      首先,如果锁被释放,第二个将等待少于 5 毫秒,因为它不需要等待从 sleep 唤醒。因此,它较少受到饥饿问题的影响。

      然后,j.u.c.l 包使用 LockSupport#park 方法暂停线程,而不是 Thread.sleep。据我了解,它在线程调度程序上有所不同,park 允许更低的延迟,但不确定sleep 是如何实现的。

      另外,你的代码没有任何意义,通过lock()方法可以达到完全相同的效果。

      【讨论】:

      • 除非有一些其他代码需要在这个线程上每五毫秒执行一次。
      • @ErickRobertson 对,这就是重点!我想使用其中一个 sn-ps 来避免死锁。我想我需要睡觉了,所以其他线程可以在此期间取得进展并希望释放另一个锁。
      • @Chriss 引擎盖下的lock() 调用java.util.concurrent.locks.LockSupport#park,它“禁用当前线程以进行线程调度,除非许可可用。”。这意味着所有其他线程都应该进行,当前线程处于休眠状态。我看不出它如何帮助解决死锁,只能通过降低死锁概率,以增加延迟为代价收紧竞争条件窗口。
      猜你喜欢
      • 2023-03-06
      • 2013-05-20
      • 1970-01-01
      • 2019-12-17
      • 1970-01-01
      • 1970-01-01
      • 2017-02-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多