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

Mik*_*ike 5 java concurrency multithreading thread-safety

这个java代码的线程安全性有什么问题吗?线程1-10通过sample.add()添加数字,而线程11-20调用removeAndDouble()并将结果打印到stdout.我记得在我的脑海里有人说过,以同样的方式在removeAndDouble()中使用它来分配项目可能不是线程安全的.编译器可以优化指令,使它们不按顺序发生.这是这种情况吗?我的removeAndDouble()方法不安全吗?

从这个代码的并发角度来看还有什么问题吗?我试图用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.");
    }
}
Run Code Online (Sandbox Code Playgroud)

Sta*_*key 8

它看起来对我来说是安全的.这是我的推理.

每次访问list时都会同步进行.这很棒.即使你拔出的一部分listitem,这item是不能由多个线程访问.

只要您只list在同步时访问,您应该是好的(在您当前的设计中).


mwh*_*den 6

您的同步很好,不会导致任何乱序执行问题.

但是,我注意到一些问题.

首先,如果你在in 之后添加一个,你的waitUntilEmpty方法会更加及时.这将消除你的最多10秒延迟.list.notifyAll()list.remove(0)removeAndDoublewait(10000)

其次,你的list.notifyin add(Integer)应该是a notifyAll,因为notify只唤醒一个线程,它可能唤醒一个在里面等待的线程waitUntilEmpty而不是waitUntilNotEmpty.

第三,以上都不是你的应用程序的活跃终结,因为你使用了有限的等待,但如果你做了上面的两个更改,你的应用程序将有更好的线程性能(waitUntilEmpty)和有限的等待变得不必要并且可以变得普通的旧的没有 - arg等待.