Как смоделировать ситуацию, когда i++ поврежден из-за одновременного выполнения потоков?

Если операции увеличения / уменьшения int не являются атомарными в Java 6, то есть говорят, что они выполняются в несколько этапов (чтение значения, увеличение, запись и т. Д.), Я хотел бы увидеть фрагмент кода, который продемонстрирует, насколько многократным Потоки могут воздействовать на одну переменную типа int таким образом, чтобы полностью ее повредить.

Например, элементарные шаги включают, но не охватывают все, это: i++ ~= положить i в регистр; инкремент i (инкрементная операция); напиши мне обратно в память;

если два или более потоков чередуются во время процесса, это, вероятно, будет означать, что значение после двух последовательных обращений к i ++ будет увеличено только один раз.

Можете ли вы продемонстрировать кусок кода в Java, который моделирует эту ситуацию в среде многопоточности?

3 ответа

public class Test {
    private static int counter;

    public static void main(String[] args) throws InterruptedException {
        Runnable r = new Runnable() {
            public void run() {
                for (int i = 0; i < 100000; i++) {
                    counter++;
                }
            }
        };
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        if (counter != 200000) {
            System.out.println("Houston, we have a synchronization problem: counter should be 200000, but it is " + counter);
        }
    }
}

Запуск этой программы на моей машине дает

Houston, we have a synchronization problem: counter should be 200000, but it is 198459

Вам нужно запустить тест для многих итераций, так как ++ выполняется быстро и может завершиться до того, как возникнет проблема.

public static void main(String... args) throws InterruptedException {
    for (int nThreads = 1; nThreads <= 16; nThreads*=2)
        doThreadSafeTest(nThreads);
}

private static void doThreadSafeTest(final int nThreads) throws InterruptedException {
    final int count = 1000 * 1000 * 1000;

    ExecutorService es = Executors.newFixedThreadPool(nThreads);
    final int[] num = {0};
    for (int i = 0; i < nThreads; i++)
        es.submit(new Runnable() {
            public void run() {
                for (int j = 0; j < count; j += nThreads)
                    num[0]++;
            }
        });
    es.shutdown();
    es.awaitTermination(10, TimeUnit.SECONDS);
    System.out.printf("With %,d threads should total %,d but was %,d%n", nThreads, count, num[0]);
}

печать

With 1 threads should total 1,000,000,000 but was 1,000,000,000
With 2 threads should total 1,000,000,000 but was 501,493,584
With 4 threads should total 1,000,000,000 but was 319,482,716
With 8 threads should total 1,000,000,000 but was 261,092,117
With 16 threads should total 1,000,000,000 but was 202,145,371

только с 500K я получил следующее на базовом ноутбуке. На более быстрой машине вы можете иметь большее число итераций, прежде чем возникнет проблема.

With 1 threads should total 500,000 but was 500,000
With 2 threads should total 500,000 but was 500,000
With 4 threads should total 500,000 but was 500,000
With 8 threads should total 500,000 but was 500,000
With 16 threads should total 500,000 but was 500,000

Вот код Извини за staticПросто хотел сохранить несколько строк кода, это не повлияет на результаты:

public class Test
{
    public static int value;

    public static void main(String[] args) throws InterruptedException
    {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for(int i = 0; i < 50000; ++i)
                    ++value;
            }
        };

        List<Thread> threads = new ArrayList<Thread>();
        for(int j = 0; j < 2; ++j)
            threads.add(new Thread(r));
        for (Thread thread : threads)
            thread.start();
        for (Thread thread : threads)
            thread.join();

        System.out.println(value);
    }
}

Эта программа может напечатать что-нибудь между 50000 и 100000, но на моей машине она никогда не печатала 100000.

Теперь замени int с AtomicInteger а также incrementAndGet() метод. Он всегда печатает 100000 без большого влияния на производительность (он использует инструкции CPU CAS, без синхронизации Java).

Другие вопросы по тегам