理解Java并发中的外来方法

St.*_*rio 7 java multithreading

我正在阅读J. Bloch的有效Java,现在我正在关于外来方法的部分.

我正在尝试理解Java并发中的外来方法及其可能造成的危害.正如他所说,我们基本上不知道外星人的方法可以做什么,我们可能最终陷入僵局.我试图重现这样一个死锁行为编写以下简单的应用程序(为简单起见,外来方法在同一个类中):

public class App {
    private static StringBuffer lines = new StringBuffer();

    public static void modifyLines(){
        System.out.println("Invocation modifyLines() started by " + Thread.currentThread().getName());
        synchronized (lines) {
            System.out.println("Entering modifyLines() synchronized " + Thread.currentThread().getName());
            lines.append("Modified");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        synchronized (lines) {
            System.out.println("Entering main() synchronized by " + Thread.currentThread().getName());
            alienMethod();
        }
    }

    public static void alienMethod(){
        ExecutorService es = Executors.newSingleThreadExecutor();
        es.submit(new Runnable() {
            @Override
            public void run() {
                modifyLines();
            }
        });
        es.shutdown();
    }
}
Run Code Online (Sandbox Code Playgroud)

我预计会发生死锁,并且通过调用生成的线程alienMethod()永远不会进入同步块modifyLines().但该程序打印以下内容:

Entering main() synchronized by main
Invocation modifyLines() started by pool-1-thread-1
Entering modifyLines() synchronized pool-1-thread-1
Run Code Online (Sandbox Code Playgroud)

这意味着没有发生僵局.为什么?外星人方法示例有什么问题?

hag*_*wal 7

这是一个非常古老的问题,答案也被接受,但我不得不花时间挖掘这个古老的坟墓,因为我认为答案并不完全正确,可能会产生误导。

首先让我强调已接受的答案是如何被破坏的——如果你运行了,es.awaitTermination(2, TimeUnit.SECONDS);那么你就不会死锁,因为在给定的代码中从来没有死锁情况。当您有 2 个线程等待对方释放锁时,就会发生死锁,通常要发生死锁,您将至少有 2 个锁和 2 个线程。根据建议的答案,发生的事情是主线程被使用awaitTermination并且由于主线程持有锁所以新线程必须“等待”直到主线程释放锁,现在Long.MAX_VALUE这个等待期太大了所以看起来像是僵局,但实际上是在“等待”而不是僵局,死锁和等待是有区别的,为了解决死锁,你必须调整你的锁定代码。

现在来到陌生方法:基本上是一个类的方法将被视为“外国人法”,如果没有它,或在方法换句话说实施细则的任何信息,现在一般每个班级不会有任何信息关于其他类的实现(这也是预期的 - “松散耦合”)所以“如此”类 B 的每个方法对于类 A 都是“异类”,但我们不认为它在一般上下文中是异类,因为这就是预期,我们仅在同步上下文中将方法称为外来方法,所以当有一个同步并且从同步块调用一个方法时,对象没有任何信息并且对象不能确定它是否会导致死锁,那么该方法被称为“外来方法”。

现在,下面是一个示例代码来演示(*阅读标记为“*****”*的代码注释)外星人方法,它表明如何根据客户端方法正在执行的操作,可能会发生死锁或可能不会发生死锁发生; 在这里你有死锁而不是等待,我没有使用任何等待代码。

设置观察者.java:

public interface SetObserver {
    void added(MyClass mc, SetObserver sc);
}
Run Code Online (Sandbox Code Playgroud)

我的类.java:

import java.util.concurrent.*;

public class MyClass {

    static Object o1 = new Object();

    public void test1(){
        synchronized(o1){
            System.out.println("test1");
        }
    }

    public void test3(SetObserver sc) throws InterruptedException{
        synchronized(o1){
            for (int i = 0; i < 100; i++) {
                System.out.print("test3 >>" + i);
                sc.added(this, sc);
                synchronized(sc){
                    System.out.println("<<");
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyClass mc = new MyClass();
        mc.test3(new SetObserver() {

            @Override
            public void added(final MyClass mc, final SetObserver sc) {
                // ***** This will not cause deadlock because it doesn't spawn a new thread, even though it synchronize on same object. *****
                /*synchronized(sc){
                    mc.test1();
                }*/

                // ***** This causes a deadlock because it spawns a new thread, so it will cause lock contention among threads. *****
                ExecutorService xc = Executors.newFixedThreadPool(1);
                xc.execute(new Runnable() {

                    @Override
                    public void run() {
                        synchronized(sc){
                            System.out.println("Calling test1");
                            mc.test1();
                        }
                    }
                });
                xc.shutdown();
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 非常有用的答案!谢谢你。 (2认同)

Tag*_*eev 1

alienMethod()不会等到提交的任务完成。如果再等下去,就会陷入僵局:

public static void alienMethod() throws InterruptedException{
    ExecutorService es = Executors.newSingleThreadExecutor();
    es.submit(new Runnable() {
        @Override
        public void run() {
            modifyLines();
        }
    });
    es.shutdown();
    es.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
Run Code Online (Sandbox Code Playgroud)