简体   繁体   English

将对象分配给在同步块外部定义的字段 - 它是否是线程安全的?

[英]Assigning a object to a field defined outside a synchronized block - is it thread safe?

Is there anything wrong with the thread safety of this java code? 这个java代码的线程安全性有什么问题吗? Threads 1-10 add numbers via sample.add(), and Threads 11-20 call removeAndDouble() and print the results to stdout. 线程1-10通过sample.add()添加数字,而线程11-20调用removeAndDouble()并将结果打印到stdout。 I recall from the back of my mind that someone said that assigning item in same way as I've got in removeAndDouble() using it outside of the synchronized block may not be thread safe. 我记得在我的脑海里有人说过,以同样的方式在removeAndDouble()中使用它来分配项目可能不是线程安全的。 That the compiler may optimize the instructions away so they occur out of sequence. 编译器可以优化指令,使它们不按顺序发生。 Is that the case here? 这是这种情况吗? Is my removeAndDouble() method unsafe? 我的removeAndDouble()方法不安全吗?

Is there anything else wrong from a concurrency perspective with this code? 从这个代码的并发角度来看还有什么问题吗? I am trying to get a better understanding of concurrency and the memory model with java (1.6 upwards). 我试图用java(1.6向上)更好地理解并发性和内存模型。

import java.util.*;
import java.util.concurrent.*;

public class Sample {

    private final List<Integer> list = new ArrayList<Integer>();

    public void add(Integer o) {
        synchronized (list) {
            list.add(o);
            list.notify();
        }
    }

    public void waitUntilEmpty() {
        synchronized (list) {
            while (!list.isEmpty()) {
                try { 
                    list.wait(10000);  
                 } catch (InterruptedException ex) { }
            }
        }
    }

    public void waitUntilNotEmpty() {
        synchronized (list) {
            while (list.isEmpty()) {
                try { 
                    list.wait(10000);  
                 } catch (InterruptedException ex) { }
            }
        }
    }

    public Integer removeAndDouble() {
        // item declared outside synchronized block
        Integer item; 
        synchronized (list) { 
            waitUntilNotEmpty();
            item = list.remove(0);
        }
        // Would this ever be anything but that from list.remove(0)?
        return Integer.valueOf(item.intValue() * 2);
    }

    public static void main(String[] args) {
        final Sample sample = new Sample();

        for (int i = 0; i < 10; i++) {
            Thread t = new Thread() {
                public void run() {
                    while (true) {
                        System.out.println(getName()+" Found: " + sample.removeAndDouble());
                    }
                }
            };
            t.setName("Consumer-"+i);
            t.setDaemon(true);
            t.start();
        }

        final ExecutorService producers = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            final int j = i * 10000;
            Thread t = new Thread() {
                public void run() {
                    for (int c = 0; c < 1000; c++) {
                        sample.add(j + c);
                    }
                }
            };
            t.setName("Producer-"+i);
            t.setDaemon(false);
            producers.execute(t);
        }

        producers.shutdown();
        try {
            producers.awaitTermination(600, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        sample.waitUntilEmpty();        
        System.out.println("Done.");
    }
}

It looks thread safe to me. 它看起来对我来说是安全的。 Here is my reasoning. 这是我的推理。

Everytime you access list you do it synchronized. 每次访问list时,都会同步进行。 This is great. 这很棒。 Even though you pull out a part of the list in item , that item is not accessed by multiple threads. 即使您在item提取list的一部分,多个线程也不会访问该item

As long as you only access list while synchronized, you should be good (in your current design.) 只要你只在同步时访问list ,你应该是好的(在你当前的设计中。)

Your synchronization is fine, and will not result in any out-of-order execution problems. 您的同步很好,不会导致任何乱序执行问题。

However, I do notice a few issues. 但是,我注意到一些问题。

First, your waitUntilEmpty method would be much more timely if you add a list.notifyAll() after the list.remove(0) in removeAndDouble . 首先,如果在removeAndDoublelist.remove(0)之后添加list.notifyAll() ,那么waitUntilEmpty方法会更加及时 This will eliminate an up-to 10 second delay in your wait(10000) . 这将消除wait(10000)最多10秒的延迟。

Second, your list.notify in add(Integer) should be a notifyAll , because notify only wakes one thread, and it may wake a thread that is waiting inside waitUntilEmpty instead of waitUntilNotEmpty . 其次,你的list.notify in add(Integer)应该是一个notifyAll ,因为notify只唤醒一个线程,它可能唤醒一个在waitUntilEmptywaitUntilEmpty而不是waitUntilNotEmpty

Third, none of the above is terminal to your application's liveness, because you used bounded waits, but if you make the two above changes, your application will have better threaded performance ( waitUntilEmpty ) and the bounded waits become unnecessary and can become plain old no-arg waits. 第三,以上都不是你的应用程序的活跃终结,因为你使用了有限的等待,但是如果你做了上面的两个更改,你的应用程序将有更好的线程性能( waitUntilEmpty )和有限的等待变得不必要并且可以变得普通老了没有-arg等待。

Your code as-is is in fact thread safe. 您的代码实际上是线程安全的。 The reasoning behind this is two part. 这背后的原因是两部分。

The first is mutual exclusion. 首先是互斥。 Your synchronization correctly ensures that only one thread at a time will modify the collections. 您的同步正确确保一次只有一个线程将修改集合。

The second has to do with your concern about compiler reordering. 第二个问题与您对编译器重新排序的关注有关。 Youre worried that the compile can in fact re order the assigning in which it wouldnt be thread safe. 你担心编译实际上可以重新命令它不是线程安全的分配。 You dont have to worry about it in this case. 在这种情况下你不必担心它。 Synchronizing on the list creates a happens-before relationship. 在列表上进行同步会创建一个先发生过的关系。 All removes from the list happens-before the write to Integer item . 在写入Integer item之前,将从列表中删除所有内容。 This tells the compiler that it cannot re order the write to item in that method. 这告诉编译器它无法重新命令该方法中的写入项。

Your code is thread-safe, but not concurrent (as in parallel). 您的代码是线程安全的,但不是并发的(并行)。 As everything is accessed under a single mutual exclusion lock, you are serialising all access, in effect access to the structure is single-threaded. 由于在单个互斥锁下访问所有内容,您将序列化所有访问权限,实际上对结构的访问是单线程的。

If you require the functionality as described in your production code, the java.util.concurrent package already provides a BlockingQueue with (fixed size) array and (growable) linked list based implementations. 如果您需要生产代码中描述的功能,则java.util.concurrent包已经提供了具有(固定大小)数组和(可增长的)基于链表的实现的BlockingQueue These are very interesting to study for implementation ideas at the very least. 至少在研究实施思路时,这些非常有趣。

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

相关问题 在 object Y 上定义的同步块中访问 object X 是否安全? - Is it safe to access an object X within a synchronized block defined on object Y? 如果在同步块之外声明/返回了返回值,该方法是否是线程安全的? - Is a method thread-safe if the return value is declared/returned outside of a synchronized block? ArrayList即使同步块也不安全吗? - ArrayList is not Thread safe even with synchronized block? 同步语句之外的代码语句是否是线程安全的? - Are code statements that are outside the synchronized statement thread-safe? 是否遍历在同步块中检索的列表是线程安全的? - Is iterating over a list retrieved in a synchronized block thread-safe? 在线程安全 Singleton 中,返回是否必须在同步块内 - In a Thread Safe Singleton does the return have to be inside the synchronized block 基于该对象在同步块内分配对象(Java) - Assigning an object within a synchronized block based on that object (Java) 我可以在Java中的同步块之外的共享字段上检查null吗? - Can I check for null on a shared field outside of a synchronized block in java? 如何保证对对象的所有非线程安全引用都将被同步? - How to guarantee that all non-thread-safe references to an object will be synchronized? Java线程同步块 - java thread synchronized block
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM