3

当我们调用lock.lock()或者尝试进入一个synchronized块时,如果其他线程已经获取了该锁,我们的线程就会阻塞。现在我的问题是,当我们查看它的实现时,lock.lock()它会将获取锁委托给 AQS,而 AQS 实际上会驻留当前线程(因此调度程序无法进一步调度它)。

synchronized阻塞也一样吗?

我什至认为我的线程状态也不同。例如,如果我的线程在synchronizedblock上被阻塞,那么BLOCKING如果我调用 lock.lock()了 ,那么它将是WAITING. 我对吗?

Thread.status我担心的是以下两种锁定策略在通过停车而不是忙等待来提高性能方面的区别

  1. ReentrantLock.lock();
  2. synchronize { /*some code */ }
4

3 回答 3

4

BLOCKING - 在资源上被阻塞,不能被中断

WAITING - 在资源上被阻止,但可以被中断或通知或取消驻留。

如您所见,WAITING 更适合来自另一个处理的控制。例如,如果两个线程死锁,您可以通过中断来中断 lock()。使用同步的两个线程时,您会被卡住。

同步与锁的行为非常相似,并且主要版本之间的确切细节会发生变化。

我的建议是使用

  • 同步用于需要线程安全但锁争用非常低的更简单的代码。

  • 在您确定存在锁争用的情况下使用 Lock,或者您需要额外的功能,例如 tryLock。


如果你这样做

final Lock lock = new ReentrantLock();
lock.lock();
Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
t.start();
Thread.sleep(100);
System.out.println(t + " is " + t.getState());
lock.unlock();

印刷

Thread[Thread-0,5,main] is WAITING

Thread.State状态


等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:

  • Object.wait 没有超时
  • 没有超时的 Thread.join
  • LockSupport.park

处于等待状态的线程正在等待另一个线程执行特定操作。例如,一个对对象调用 Object.wait() 的线程正在等待另一个线程对该对象调用 Object.notify() 或 Object.notifyAll()。已调用 Thread.join() 的线程正在等待指定线程终止。

于 2013-06-21T11:35:28.677 回答
4

调用locklockInterruptibly将线程置于WAITING状态:

等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:

  • Object.wait 没有超时
  • 没有超时的 Thread.join
  • LockSupport.park

以下代码启动四个线程,前两个(A,B)运行相同的代码并通过该lock方法锁定一些监视器。另外两个 (C,D) 也运行相同的代码,但它们通过以下lockInterruptibly方法锁定了另一个监视器:

public static synchronized void dumpThreadState(List<Thread> threads) {
    System.out.println("thread state dump start");
   for (Thread t: threads) {
        System.out.println(t.getName()+" "+t.getState());
    } 
   System.out.println("thread state dump end\n");
}

public static void main(String[] args) throws InterruptedException {
    final Lock lock = new ReentrantLock();
    final Lock anotherLock = new ReentrantLock();
    List<Thread> threads = new LinkedList<Thread>();
    
    Runnable first = new Runnable() {

        @Override
        public void run() {                
            try {
                lock.lock();
            } 
            catch (Exception ex) {
                System.out.println(Thread.currentThread().getName()+" processing exception "+ex.getClass().getSimpleName());                    
            }
            while (true);                
        }
    } ;
    Runnable second = new Runnable() {

        @Override
        public void run() {         
            try {
                anotherLock.lockInterruptibly();
            } 
            catch (InterruptedException ex) {
                System.out.println(Thread.currentThread().getName()+" was interrupted");
            }
            while (true); 
        }
    };
    
    threads.add(new Thread(first,"A"));
    threads.add(new Thread(first,"B"));
    threads.add(new Thread(second,"C"));
    threads.add(new Thread(second,"D"));
           
    
    dumpThreadState(threads);
    
    for (Thread t: threads) {
        t.start();
    }
    
    Thread.currentThread().sleep(100);
    
    dumpThreadState(threads);
    
    System.out.println("interrupting " + threads.get(1).getName());
    threads.get(1).interrupt();
     
    dumpThreadState(threads);
    
    System.out.println("interrupting " + threads.get(3).getName());
    threads.get(3).interrupt();
    
    Thread.currentThread().sleep(100);
    dumpThreadState(threads);
    
    for (Thread t: threads) {
        t.join();
    }
}

它输出:

thread state dump start
A NEW
B NEW
C NEW
D NEW
thread state dump end

thread state dump start
A RUNNABLE
B WAITING
C RUNNABLE
D WAITING
thread state dump end

interrupting B
thread state dump start
A RUNNABLE
B WAITING
C RUNNABLE
D WAITING
thread state dump end

interrupting D
D was interrupted
thread state dump start
A RUNNABLE
B WAITING
C RUNNABLE
D RUNNABLE
thread state dump end

可以看出,通过该lock方法锁定的线程不能被中断,而通过该方法锁定的线程lockInterruptibly可以。

在另一个示例中,启动了三个线程,前两个(A,B)运行相同的代码并通过synchronized块锁定相同的监视器。第三个线程锁定在另一个监视器上,但通过以下wait方法等待:

 public static void main(String[] args) throws InterruptedException {
    final Object lock = new Object();
    final Object anotherLock = new Object();
    
    List<Thread> threads = new LinkedList<Thread>();
    
    Runnable first = new Runnable() {

        @Override
        public void run() {
            synchronized(lock) {
                while (true);
            }
        }
    } ;
    Runnable second = new Runnable() {

        @Override
        public void run() {         
            synchronized(anotherLock) {
                try {
                    anotherLock.wait();
                } 
                catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
    };
    
    threads.add(new Thread(first,"A"));
    threads.add(new Thread(first,"B"));
    threads.add(new Thread(second,"C"));
    
    dumpThreadState(threads);
    
    for (Thread t: threads) {
        t.start();
    }
    
    Thread.currentThread().sleep(100);
    
    dumpThreadState(threads);
    
    for (Thread t: threads) {
        t.join();
    }
    
}

它输出:

thread state dump start
A NEW
B NEW
C NEW
thread state dump end
thread state dump start
A RUNNABLE
B BLOCKED
C WAITING
thread state dump end

线程 C 结束于WAITING状态,而线程 B 结束于BLOCKING状态:

线程阻塞等待监视器锁的线程状态。处于阻塞状态的线程正在等待监视器锁进入同步块/方法或调用 Object.wait 后重新进入同步块/方法。

编辑:

这是一个非常好的线程状态UML图

于 2013-06-21T13:25:01.327 回答
3

停放线程和同步阻塞是非常不同的。当您尝试输入同步块时,您明确地尝试获取对象实例上的监视器。如果您无法获取监视器,您的线程将进入 BLOCKING 状态,直到监视器可用。停车更类似于Object.wait()方法,因为代码知道在其他条件变为真之前它不能继续。在这里阻止是没有意义的,因为这将是徒劳的,因为我继续前进的条件目前是真实的。在这一点上,我进入 WAITING 或 TIMED_WAITING (取决于如何发出等待)状态,直到我收到通知(通过类似的东西notify()notifyAll()unpark())。一旦我的条件变为真,如果我处于等待状态,我就会出来,然后可能会尝试获取监视器并在需要时进入 BLOCKING。如果我得到我的显示器,我会进入 RUNNING 并继续我的快乐方式

所以等待真的是知道我不能做某事,并让其他线程在它认为我可以时通知我。不过,我醒来后可能会导致阻塞。阻塞只是在没有明确的其他先决条件的情况下竞争对监视器的访问。

lock()Lock实例上调用时,调用线程实际上处于等待状态并且没有阻塞。这里的好处是可以中断这种等待状态,这有助于避免死锁。使用类之Lock类的东西,您可以通过tryLock()tryLock(long,TimeUnit)lock()来选择所需的等待行为lockInterruptibly()。您可以指定要等待多长时间以及是否可以通过调用的方法中断。使用synchronized代码,您没有这样的选择。你正在阻塞并且你被阻塞直到某个线程放弃了你想要的监视器,如果它永远不会,你就会陷入僵局。这就是为什么从 Java 5 和concurrent包开始,你应该避免使用synchronized关键字,而是尝试使用 和 之类的东西实现类似的Lock语义Condition

于 2013-06-21T11:33:56.193 回答