代码之家  ›  专栏  ›  技术社区  ›  Mike

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

  •  5
  • Mike  · 技术社区  · 16 年前

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

    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.");
        }
    }
    
    4 回复  |  直到 16 年前
        1
  •  6
  •   Peter Mortensen Pieter Jan Bonestroo    16 年前

    不过,我确实注意到一些问题。

    waitUntilEmpty 方法会更有效 如果你加一个 list.notifyAll() 之后 list.remove(0) 在里面 removeAndDouble . 这将消除最长10秒的延迟 wait(10000) .

    第二,你的 list.notify 在里面 add(Integer) 应该是一个 notifyAll ,因为 notify 只会醒来 一 线程,它可能会唤醒正在内部等待的线程 等待时间 waitUntilNotEmpty .

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

        2
  •  8
  •   Starkey    16 年前

    每次你访问 list 你同步完成。太棒了。即使你拿出一部分 列表 在里面 item 没有被多个线程访问。

    只要你能进入 同步时,您应该是优秀的(在您当前的设计中)

        3
  •  3
  •   tangens    16 年前

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

    首先是互斥。正确的同步可以确保一次只有一个线程修改集合。

    第二个问题与编译器重新排序有关。您担心编译实际上会重新排序赋值,这样它就不会是线程安全的。在这种情况下你不用担心。在列表上同步将创建“发生在”关系。所有从列表中删除的操作都发生在写入之前 Integer item . 这告诉编译器它不能在该方法中对write-to项重新排序。

        4
  •  1
  •   Jed Wesley-Smith    16 年前

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

    如果您需要生产代码中描述的功能,则 java.util.concurrent BlockingQueue 使用(固定大小)数组和(可增长的)基于链表的实现。这些都是非常有趣的研究,至少实现的想法。