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

Java 将对象分配给在同步块外部定义的字段-是否线程安全?,java,multithreading,concurrency,thread-safety,Java,Multithreading,Concurrency,Thread Safety,这个java代码的线程安全性有什么问题吗?线程1-10通过sample.add()添加数字,线程11-20调用removeAndDouble()并将结果打印到stdout。我记得有人说过,在同步块之外使用removeAndDouble()以与我在removeAndDouble()中相同的方式分配项可能不是线程安全的。编译器可能会对指令进行优化,以使它们不按顺序出现。这里是这样吗?我的removeAndDouble()方法不安全吗 从并发性的角度来看,这段代码还有其他错误吗?我试图通过java(1

这个java代码的线程安全性有什么问题吗?线程1-10通过sample.add()添加数字,线程11-20调用removeAndDouble()并将结果打印到stdout。我记得有人说过,在同步块之外使用removeAndDouble()以与我在removeAndDouble()中相同的方式分配项可能不是线程安全的。编译器可能会对指令进行优化,以使它们不按顺序出现。这里是这样吗?我的removeAndDouble()方法不安全吗

从并发性的角度来看,这段代码还有其他错误吗?我试图通过java(1.6以上)更好地理解并发性和内存模型

import java.util.*;
导入java.util.concurrent.*;
公共类样本{
私有最终列表=新的ArrayList();
公共空添加(整数o){
已同步(列表){
列表。添加(o);
list.notify();
}
}
公共无效waitUntilEmpty(){
已同步(列表){
而(!list.isEmpty()){
试试{
列表。等待(10000);
}catch(InterruptedException ex){}
}
}
}
公共void waitUntilNotEmpty(){
已同步(列表){
while(list.isEmpty()){
试试{
列表。等待(10000);
}catch(InterruptedException ex){}
}
}
}
公共整数removeAndDouble(){
//在同步块外声明的项
整数项;
已同步(列表){
waitUntilNotEmpty();
项目=列表。删除(0);
}
//是否会从列表中删除(0)?
返回整数.valueOf(item.intValue()*2);
}
公共静态void main(字符串[]args){
最终样品=新样品();
对于(int i=0;i<10;i++){
线程t=新线程(){
公开募捐{
while(true){
System.out.println(getName()+”找到:“+sample.removeAndDouble());
}
}
};
t、 集合名(“消费者-”+i);
t、 setDaemon(true);
t、 start();
}
最终ExecutorService生产者=Executors.newFixedThreadPool(10);
对于(int i=0;i<10;i++){
最终整数j=i*10000;
线程t=新线程(){
公开募捐{
对于(int c=0;c<1000;c++){
样品.添加(j+c);
}
}
};
t、 集合名(“生产者-”+i);
t、 setDaemon(false);
生产者。执行(t);
}
生产者。关闭();
试一试{
生产者。等待终止(600,时间单位。秒);
}捕捉(中断异常e){
e、 printStackTrace();
}
sample.waitUntilEmpty();
System.out.println(“完成”);
}
}

我觉得它是线程安全的。这是我的理由

每次访问
列表
时,都会同步执行该操作。这太棒了。即使您在
项目
中拉出
列表
的一部分,该
项目
也不会被多个线程访问


只要您在同步时只访问
列表
,就应该很好(在您当前的设计中)。

您的代码实际上是线程安全的。这背后的原因有两部分

首先是相互排斥。您的同步正确地确保了一次只有一个线程将修改集合


第二个问题与您对编译器重新排序的担忧有关。您担心编译实际上会重新安排分配顺序,而在这种情况下它不会是线程安全的。在这种情况下你不必担心。在列表上同步将创建“发生在之前”关系。所有从列表中删除的操作都发生在写入
整数项之前。这会告诉编译器它无法在该方法中对写入项重新排序。

您的同步状态良好,不会导致任何无序执行问题

然而,我确实注意到一些问题

首先,如果在
列表之后添加
list.notifyAll()
,则您的
waitUntilEmpty
方法将更加及时。在
removeanddough
中删除(0)
。这将消除
等待(10000)
过程中长达10秒的延迟

其次,您的
列表.notify
add(Integer)
中应该是
notifyAll
,因为
notify
只唤醒一个线程,它可能唤醒在
waitUntilEmpty
中等待的线程,而不是
waitUntilNotEmpty


第三,以上这些都不是应用程序活性的终点,因为您使用了有界等待,但如果您进行了上述两项更改,您的应用程序将具有更好的线程性能(
waitUntilEmpty
),并且有界等待将变得不必要,并且可以变成普通的无参数等待。

您的代码是线程安全的,但不是并行的(如并行的)。由于所有内容都是在单个互斥锁下访问的,因此您正在序列化所有访问,实际上,对结构的访问是单线程的


如果您需要生产代码中描述的功能,那么
java.util.concurrent
包已经提供了一个
BlockingQueue
,它具有(固定大小)数组和(可扩展的)基于链表的实现。至少对于实现思想来说,这些都是非常有趣的。

notifyAll
上的调用很好。我不是序列化所有访问,只是访问队列。生产者和消费者是平行的(在一个典型的场景中会消耗更多的处理时间)
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.");
    }
}