java - java并发中的alien方法难懂

标签 java concurrency jvm

我正在看《七周内的七个并发模型》一书,在第2章中有关于alien方法的描述。

class Downloader extends Thread
{
    private InputStream in;
    private OutputStream out;
    private ArrayList<ProgressListener> listeners;

    public Downloader(URL url, String outputFilename)
            throws IOException
    {
        in = url.openConnection().getInputStream();
        out = new FileOutputStream(outputFilename);
        listeners = new ArrayList<ProgressListener>();
    }

    public synchronized void addListener(ProgressListener listener)
    {
        listeners.add(listener);
    }

    public synchronized void removeListener(ProgressListener listener)
    {
        listeners.remove(listener);
    }

    private synchronized void updateProgress(int n)
    {
        for (ProgressListener listener : listeners)
            listener.onProgress(n);
    }


    public void run () {
        int n = 0, total = 0;
        byte[] buffer = new byte[1024];
        try
        {
            while ((n = in.read(buffer)) != -1)
            {
                out.write(buffer, 0, n);
                total += n;
                updateProgress(total);
            }
            out.flush();
        }
        catch (IOException e)
        {
        }
    }
}

Because addListener(), removeListener(), and updateProgress() are all synchronized, multiple threads can call them without stepping on one another’s toes. But a trap lurks in this code that could lead to deadlock even though there’s only a single lock in use. The problem is that updateProgress() calls an alien method—a method it knows nothing about. That method could do anything, including acquiring another lock. If it does, then we’ve acquired two locks without knowing whether we’ve done so in the right order. As we’ve just seen, that can lead to deadlock. The only solution is to avoid calling alien methods while holding a lock. One way to achieve this is to make a defensive copy of listeners before iterating through it:

private void updateProgress(int n) { 
    ArrayList<ProgressListener> listenersCopy; 
    synchronized(this) {
        listenersCopy = (ArrayList<ProgressListener>)listeners.clone();
    }
    for (ProgressListener listener: listenersCopy)
        listener.onProgress(n);
}

看了这个解释,我还是不明白onProgress方法怎么会造成死锁,为什么clone监听列表可以避免这个问题。

最佳答案

I still can't understand how the onProgress method can cause dead lock

假设我们有一个 ProgressListener 实现

Downloader  downloader = new Downloader();

downloader.addListener(new ProgressListener(){
    public void onProgress(int n) { 
        // do something
        Thread th = new Thread(() -> downloader.addListener(() -> {});
        th.start();
        th.join();
    }
});

downloader.updateProgress(10);

第一次调用 addListener 会成功。但是,当您调用 updateProgress 时,将触发 onProgress 方法。当 onProgress 被触发时,它永远不会完成,因为正在调用 addListener 方法(在 sync 方法上阻塞),而 onProgress 仍在获取锁。这会导致死锁。

现在,这个示例是一个愚蠢的示例,因为实际上不会尝试创建死锁,但是复杂的代码路径和共享逻辑很容易导致某种形式的死锁。这里的重点是永远不要让自己处于那个位置

why clone the listener list can avoid the problem.

您希望同步访问集合,因为它由多个线程共享和改变。通过创建克隆,您不再允许其他线程改变您的集合(线程本地克隆)并且可以安全地遍历。

您仍然在读取时同步克隆,但 onProgress 的执行发生在 同步 之外。当您这样做时,我列出的示例将永远不会死锁,因为只有一个线程将获取 Downloaded 监视器。

关于java - java并发中的alien方法难懂,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56529352/

相关文章:

java - 在 Hibernate 中,为什么 Set 是表示多值关联的推荐方式

java - 找不到资源错误 404

java - 导出的 Eclipse 代码格式首选项;在另一台计算机上导入但格式仍然不同

java - Java框架中不同类和接口(interface)之间关系的可视化

java - 是否有 API 可以检查系统是否是 android,而不是桌面 jvm?

java - 为什么这个 Java for 循环终止,尽管它应该无限循环?

java - 连接正忙于处理另一个 hstmt 的结果

concurrency - Azure Functions 并发和缩放行为

c# - 我需要等待回调吗?

java - JVM 垃圾收集突然占用大量 CPU