为啥看起来两个线程正在访问我的代码中的一个锁?

Posted

技术标签:

【中文标题】为啥看起来两个线程正在访问我的代码中的一个锁?【英文标题】:why does it seem like two threads are accessing one lock in my code?为什么看起来两个线程正在访问我的代码中的一个锁? 【发布时间】:2019-07-26 08:08:42 【问题描述】:

我正在尝试学习java多线程,这是一个简单的leetcode并发问题(https://leetcode.com/problems/print-foobar-alternately/)。我想出了以下代码,但我不明白它为什么起作用。问题是一个线程调用foo,一个线程调用bar,它应该打印“foobar”n次。

public class Foobar 
    int n;
    boolean hasFoo;
    Lock lock;
    Condition cond;
    public Foobar(int n) 
        this.n = n;
        hasFoo = false;
        lock = new ReentrantLock();
        cond = lock.newCondition();
    

    public void foo(Runnable printFoo) throws InterruptedException 
        for (int i = 0; i < n; i++) 
            lock.lock();
            while (hasFoo) 
                cond.await();
            
            printFoo.run();
            hasFoo = true;
            cond.signalAll();
            lock.unlock();
        
    

    public void bar(Runnable printBar) throws InterruptedException 
        for (int i = 0; i < n; i++) 
            lock.lock();
            while (!hasFoo) 
                cond.await();
            
            printBar.run();
            hasFoo = false;
            cond.signalAll();
            lock.unlock();
        
    

它有效,但我不明白为什么。据我了解,如果“bar”线程首先运行并获得锁,它应该等待并且“foo”线程将阻塞在lock.lock(); 行,但事实证明它们都进入了锁定部分。请赐教我在哪里误解了java中的锁。 我是这样称呼这两种方法的。

        Foobar f = new Foobar(10);
        Runnable rf = () -> 
            try 
                f.foo(() -> System.out.println("foo"));
             catch (Exception e) 
                e.printStackTrace();
            
        ;
        Runnable rp = () -> 
            try 
                f.bar(() -> System.out.println("bar"));
             catch (Exception e) 
                e.printStackTrace();
            
        ;
        Thread t1 = new Thread(rf, "foo-thread");
        Thread t2 = new Thread(rp, "bar-thread");
        t1.start();
        t2.start();

【问题讨论】:

两个线程在哪里?他们是否在FooBar 的同一个实例上工作?因为如果他们使用单独的实例,他们也会获得单独的锁(每个实例都有自己的Lock lock 字段)。 向我们展示您如何实例化和运行线程。 为什么需要这些条件?仅仅锁定锁本身并不能实现什么? 【参考方案1】:

如果“bar”线程首先运行并获得锁,它应该等待......

等什么? “bar”线程是第一个获得锁的,没有什么可等待的。该线程将立即执行下一条语句,即cond.await();,其中将释放锁1,线程将进入休眠状态。

与此同时,“foo”线程可以获取锁2,并打印它的消息,并通知其他人它的工作完成,随后解锁3 sup> 睡觉的“酒吧”。

... "foo" 线程将被阻塞在lock.lock();

没错。

但事实证明他们都进入了锁定的部分。

没有。一个进了,一个等着进。这是一个简单的锁——一次只有一个线程可以获取它(就像synchronized 语句的高级版本)。


1 与此 Condition 关联的锁被原子释放,当前线程出于线程调度目的而被禁用并处于休眠状态

https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/concurrent/locks/Condition.html#await()

2 请记住,它被阻塞了,因为它在“bar”完成后不久就试图获取锁。

如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获得锁为止。

https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/concurrent/locks/Lock.html#lock()

3 通过向所有人发出信号,the four methods 中的哪一个可以唤醒其他人。

    其他一些线程为此Condition调用signalAll()方法;

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html#await()

【讨论】:

这些条件的用例是什么?他们做了什么无法通过仅使用锁本身(这里和一般情况下)来实现? (这是一个诚实的问题,我从来没有见过以前用过的) 好的,Javadoc 解释了该部分:“条件将对象监控方法(等待、通知和 notifyAll)分解为不同的对象,以产生每个对象具有多个等待集的效果,通过将它们与任意 Lock 实现的使用结合起来。在 Lock 替换同步方法和语句的使用的地方,Condition 替换了 Object 监视器方法的使用。不过,这里似乎不需要。【参考方案2】:

bar() 中的她,Lock.lock() 获取了锁,但Condition.await() 使线程等待并释放锁。

Condition.await() 指定(重点是我的):

使当前线程等待,直到它发出信号或 被打断了。

与此条件关联的锁被原子释放并且 当前线程因线程调度目的而被禁用,并且 处于休眠状态,直到发生以下四种情况之一:

所以假设bar()foo() 之前执行:

public void bar(Runnable printBar) throws InterruptedException 
    // ...
    while (!hasFoo)  // true
      cond.await(); // release the lock and wait
    
    // ...

所以bar() 等待但foo() 在执行过程中更进一步并打印foo

public void foo(Runnable printFoo) throws InterruptedException 
  //...
  while (hasFoo)  // false
      cond.await();
  
  // We print first so

【讨论】:

"与此条件关联的锁被原子释放"

以上是关于为啥看起来两个线程正在访问我的代码中的一个锁?的主要内容,如果未能解决你的问题,请参考以下文章

线程锁

如果只有一个线程使用互斥锁,跨线程的共享内存会损坏吗?

我不知道为啥多线程编码不能减少我的代码中的计算时间

如果我需要在锁定互斥锁后解锁它,我该如何返回一个值?

为啥使用了线程池速度没有变化呢python

java synchronized