java - 了解 Java 并发中的外来方法

标签 java multithreading

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

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

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();
    }
}

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

Entering main() synchronized by main
Invocation modifyLines() started by pool-1-thread-1
Entering modifyLines() synchronized pool-1-thread-1

这意味着死锁没有发生。为什么? alien 方法示例有什么问题?

最佳答案

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

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

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

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

SetObserver.java:

public interface SetObserver {
    void added(MyClass mc, SetObserver sc);
}

MyClass.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();
            }
        });
    }
}

关于java - 了解 Java 并发中的外来方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33665253/

相关文章:

android - Delphi Firemonkey TControl.MakeScreenshot 可以在线程中工作吗?

java - 在嵌套 HashMap 中按多个键排序

java - 类加载器如何找到类

java - 有一个函数依赖于 Java 中未指定的类

ios - 在iOS上,如何在产生数据的线程上实现阻塞或 "being blocked"?

c++ - 关闭 MainWindow 后 Qt 应用程序仍在运行

c++ - memory_order_relaxed 如何在智能指针中增加原子引用计数?

java - 如何中断 Java 中的同步语句?

java renameTo 方法不起作用

java - 使用对话框编辑 RecyclerView 列表项