Назначение объекта полю, определенному вне синхронизированного блока - это потокобезопасно?

Что-нибудь не так с потокобезопасностью этого java-кода? Потоки 1–10 добавляют числа через sample.add (), а потоки 11–20 вызывают removeAndDouble () и выводят результаты на стандартный вывод. Я припоминаю, что кто-то сказал, что назначение элемента таким же образом, как у меня в 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.");
    }
}
5
задан Mike 23 September 2010 в 19:05
поделиться