2015-04-16 5 views
2

Я читаю параллелизм от Мышление на Java, 4-е издание от Bruce Eckel. Вот базовый пример кода из книги, чтобы продемонстрировать необходимость синхронизации.
Пример кода параллельного кода Java

//SerialNumberGenerator.java 

    public class SerialNumberGenerator { 
     private static volatile int serialNumber = 0; 
     public static int nextSerialNumber() { 
      return serialNumber++; // Not thread-safe 
     } 
    } 


//: concurrency/SerialNumberChecker.java 
// Operations that may seem safe are not, 
// when threads are present. 

import java.util.concurrent.*; 
// Reuses storage so we don’t run out of memory: 
class CircularSet { 
    private int[] array; 
    private int len; 
    private int index = 0; 
    public CircularSet(int size) { 
     array = new int[size]; 
     len = size; 
// Initialize to a value not produced 
// by the SerialNumberGenerator: 
     for(int i = 0; i < size; i++) 
      array[i] = -1; 
    } 
    public synchronized void add(int i) { 
     array[index] = i; 
// Wrap index and write over old elements: 
     index = ++index % len; 
    } 
    public synchronized boolean contains(int val) { 
     for(int i = 0; i < len; i++) 
      if(array[i] == val) return true; 
     return false; 
    } 
} 
public class SerialNumberChecker { 
    private static final int SIZE = 10; 
    private static CircularSet serials = 
      new CircularSet(1000); 
    private static ExecutorService exec = 
      Executors.newCachedThreadPool(); 
    static class SerialChecker implements Runnable { 
     public void run() { 
      while(true) { 
       int serial = 
         SerialNumberGenerator.nextSerialNumber(); 
       if(serials.contains(serial)) { 
        System.out.println("Duplicate: " + serial); 
        System.exit(0); 
       } 
       serials.add(serial); 
      } 
     } 
    } 
    public static void main(String[] args) throws Exception { 
     for(int i = 0; i < SIZE; i++) 
      exec.execute(new SerialChecker()); 
     // Stop after n seconds if there’s an argument: 
     if(args.length > 0) { 
      TimeUnit.SECONDS.sleep(new Integer(args[0])); 
      System.out.println("No duplicates detected"); 
      System.exit(0); 
     } 
    } 
} 

Выход он упоминается в книге любое число, как это:

Duplicate: 8468656 

Когда я запускал код, я получил результат:

Duplicate: 3484 
Duplicate: 3485 

I знаете, что программа является небезопасной и что цифры могут быть разными, но почему я получаю 2 повторяющихся последовательных значения здесь? Как это возможно?
Может ли кто-нибудь объяснить (детали низкого уровня) процесс создания повторяющихся чисел в вышеуказанной программе?

ответ

1

Высказывания

System.out.println("Duplicate: " + serial); 
System.exit(0); 

не мешают другим потокам выполнения действий между ними. Поэтому, если вы запустите н темы, призывающей небезопасный код, таким образом, потенциально выполняя эти два заявления, может быть до п нити печати их сообщения перед тем один из этих потоков удается выполнить System.exit(0);

+0

Thats for 2 Duplicates. Но как значения последовательно? Вы можете нарисовать картину для меня? –

+0

Чистое совпадение. Неудивительно, что потоки, выполняющие один и тот же небезопасный код, используют примерно одно и то же время, но это зависит от многих факторов, которые не стоит пытаться выяснить, что произошло в этом конкретном случае, когда эти два значения произошли быть последовательным. В следующий раз это может быть совершенно иначе ... – Holger

0

Он приходит из SerialNumberGenerator. Замените значение int экземпляром AtomicInteger, и вы никогда не должны получать дубликаты.

Ах .. Что Холгер написал правильно.

0

Хорошо, ответ Хольгера подходит для объяснения 2-х дубликатов, и в целом он объяснил, что небезопасный код Thread может привести к различным неопределенным возможностям. Я согласен с этим. Тем не менее, я публикую информацию на низком уровне, которая могла бы помочь объяснить одну из тех возможностей, из-за которой мог возникнуть выше код.

javap -c SerialNumberGenerator.class 

Составлено "SerialNumberGenerator.java"

public class SerialNumberGenerator { 
    public SerialNumberGenerator(); 
    Code: 
     0: aload_0 
     1: invokespecial #1     // Method java/lang/Object."<init>":()V 
     4: return 

    public static int nextSerialNumber(); 
    Code: 
     0: getstatic  #2     // Field serialNumber:I 
     3: dup 
     4: iconst_1 
     5: iadd 
     6: putstatic  #2     // Field serialNumber:I 
     9: ireturn 

    static {}; 
    Code: 
     0: iconst_0 
     1: putstatic  #2     // Field serialNumber:I 
     4: return 
} 

Как вы можете видеть здесь, есть инструкция между iadd и ireturn. Возможно, что хотя один поток загрузил значение serialNumber и заблокировал другого, он тоже загрузил бы его и когда один из них увеличит его и добавит в очередь. Другая нить, содержащая неинкрементное значение, теперь увеличивает его, чтобы получить одно и то же значение, а проверка против массива возвращает «Дублировать». Тогда, как, Хольгер сказал

Высказывания

System.out.println("Duplicate: " + serial); 
System.exit(0); 

не мешают другим потокам выполнения действий между ними. Возможно, что один поток заблокирован до того, как он выполнил выход (после печати инструкции println), а другой также выполнил инструкцию prinln. Когда планировщик вернулся к первому потоку, он вышел из него. Это типичная возможность для многих других. Цель состоит в том, чтобы визуализировать процесс, который мог привести к выходу, чтобы изображение стало более ясным.

+0

Просьба дать мне информацию о том, как я понял и ответил сам. Если что-то не так, я понял, я исправлю его и приму ответ. –

+0

Слово «заблокировано» неуместно. Нет заблокированных потоков. Кроме того, нет «очереди»; 'serialNumber' - это просто переменная. Обратите внимание, что поиск инструкций байт-кода может быть полезен здесь, но не является способом понять безопасность потоков в целом. Даже если бы был один код операции инкремента, а не эти несколько инструкций, это не означало, что это атомная инструкция. Это осталось до [* specification *] (http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4), который нужно читать и понимать. – Holger

+0

@ Хольгер: Можете ли вы взять на себя инициативу по исправлению заявлений, которые я написал? Это было бы очень полезно и ценно. –

0

пытаются использовать:

public static synchronized int nextSerialNumber() 
{ 
    return serialNumber++; 
} 

это 'синхронизируется' решить вашу проблему но вы должны использовать java.util.concurrent.atomic.AtomicInteger - лучшее решение.

Смежные вопросы