简体   繁体   English

java并发中的Alien方法很难理解

[英]Alien method in java concurrency is hard to understand

I'm reading book "Seven Concurrency Models in Seven Weeks", and there is a description about alien method in chapter 2. 我正在读“七周七个并发模型”一书,第二章有关于外来方法的描述。

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. 因为addListener(),removeListener()和updateProgress()都是同步的,所以多个线程可以调用它们而不会踩到彼此的脚趾。 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. 问题是updateProgress()调用了一个外来方法 - 一个它一无所知的方法。 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);
}

After I read this explanation, I still can't understand how the onProgress method can cause dead lock, and why clone the listener list can avoid the problem. 在我读完这个解释之后,我仍然无法理解onProgress方法如何导致死锁,以及为什么克隆监听器列表可以避免这个问题。

I still can't understand how the onProgress method can cause dead lock 我仍然无法理解onProgress方法如何导致死锁

Let's imagine we have a ProgressListener implementation of 让我们假设我们有一个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);

The first call to addListener will succeed. 第一次调用addListener将成功。 When you invoke updateProgress though, the onProgress method will be triggered. 但是,当您调用updateProgress ,将触发onProgress方法。 When the onProgress is triggered it will never complete as the addListener method is being called (blocking on the sync method) while the onProgress is still acquiring the lock. onProgress被触发时,它将永远不会完成,因为在onProgress仍在获取锁定时,正在调用addListener方法(阻塞同步方法)。 This results in a deadlock. 这导致死锁。

Now, this example is a silly one as one wouldn't actually try to create a deadlock, but complex code paths and shared logic can easily result in some form of a deadlock. 现在,这个例子是一个愚蠢的例子,因为实际上不会尝试创建死锁,但复杂的代码路径和共享逻辑很容易导致某种形式的死锁。 The point here is to never put yourself in that position 这里的要点是永远不要把自己置于那个位置

why clone the listener list can avoid the problem. 为什么克隆监听器列表可以避免这个问题。

You want to synchronize on access of the collection because it is shared and mutated by multiple threads. 您希望同步集合的访问权限,因为它由多个线程共享和变异。 By creating a clone, you are no longer allowing other threads to mutate your collection (thread local clone) and are safe to traverse. 通过创建克隆,您不再允许其他线程改变您的集合(线程本地克隆)并且可以安全地遍历。

You still synchronize on read to clone, but the execution of the onProgress is occurring outside of synchronization . 您仍然在读取时进行同步以克隆,但onProgress的执行发生在synchronization之外。 When you do that, my example I have listed will never deadlock as only one thread will be acquiring the Downloaded monitor. 当你这样做时,我列出的示例将永远不会死锁,因为只有一个线程将获取Downloaded监视器。

The point is that you never know what that method does - it could, for example, try to acquire a lock on the same instance. 关键是你永远不知道该方法的作用 - 例如,它可以尝试在同一个实例上获取锁。 If you keep holding the lock, it will die of starvation. 如果你一直拿着锁,就会死于饥饿。

Also listener.onProgress(n); 也是listener.onProgress(n); might take a lot of time to execute - if you keep holding the lock, addListener() and removeListener() are blocked for that time. 可能需要花费大量时间来执行 - 如果你继续持有锁,那么addListener()removeListener()会被阻止。 Try to release locks as soon as possible. 尝试尽快释放锁。

The advantage of copying the list is that you can call listener.onProgress(n); 复制列表的好处是你可以调用listener.onProgress(n); after releasing the lock. 释放锁后。 So it can acquite it's own lock. 所以它可以释放它自己的锁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM