2015-02-12 3 views
0

Я реализую наивную версию проблемы параллелизма между производителем и потребителем. И это потоки переключаются между ними очень быстро, а затем останавливаются около i = 50. Добавление дополнительных операторов печати по какой-либо причине позволяет JVM контексту переключать потоки и завершать программу.JVM, похоже, очень быстро отключает контекстное переключение

Почему контекст JVM не переключает потоки так, чтобы программа завершилась?

// Producer - Consumer problem 
// Producer constantly puts items into array, while consumer takes them out 

class IntBuffer { 
    private int[] buffer; 
    private int index; 

    public IntBuffer(int size) { 
     buffer = new int[size]; 
     index = 0; 
    } 

    public void add(int item) { 
     while (true) { 
      if (index < buffer.length) { 
       buffer[index] = item; 
       index++; 
       return; 
      } 
     } 
    } 

    public int remove() { 
     while (true) { 
      if (index > 0) { 
       index--; 
       int tmp = buffer[index]; 
       buffer[index] = 0; 
       return tmp; 
      } 
     } 
    } 

    public void printState() { 
     System.out.println("Index " + index); 
     System.out.println("State " + this); 
    } 

    public String toString() { 
     String res = ""; 
     for (int i = 0; i < buffer.length; i++) { 
      res += buffer[i] + " "; 
     } 
     return res; 
    } 
} 

class Producer extends Thread { 
    private IntBuffer buffer; 

    public Producer(IntBuffer buffer) { 
     this.buffer = buffer; 
    } 

    public void run() { 
     for (int i = 0; i < 1000; i++) { 
      System.out.println("added " + i); 
      buffer.add(i); 
     } 
    } 
} 

class Consumer extends Thread { 
    private IntBuffer buffer; 

    public Consumer(IntBuffer buffer) { 
     this.buffer = buffer; 
    } 

    public void run() { 
     for (int i = 0; i < 1000; i++) { 
      System.out.println("removed " + i); 
      buffer.remove(); 
     } 
    } 
} 

public class Main { 

    public static void main(String[] args) { 
     IntBuffer buf = new IntBuffer(10); 
     Thread t1 = new Thread(new Producer(buf)); 
     Thread t2 = new Thread(new Consumer(buf)); 
     t1.start(); 
     t2.start(); 
     System.out.println(buf); 
    } 
} 
+0

Где тупиковой разрешающие операторы печати идут? –

+0

Я не верю, что происходит тупик, но что потоки не являются контекстом, переключенным JVM. – countunique

+0

Просьба продемонстрировать. –

ответ

4

Ваш вопрос не обеспечивает достаточно деталей, чтобы дать ответ с уверенностью (по крайней мере, не ясно, где эти дополнительные операторы печати идут), так что я буду делать некоторые (разумные) угадывает здесь.

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

  2. Любые операции над IntBuffer не устанавливают связь между событиями, поэтому изменения, сделанные одним потоком, могут не отображаться для другого потока. Вот почему поток производителя может «поверить», что буфер заполнен, а потребительский поток «считает», что он пуст. В этом случае программа никогда не заканчивается.

Эти два утверждения не являются догадками, это факты, основанные на модели памяти Java. И здесь идет мое предположение, почему дополнительные операторы печати sorta исправить это:

  1. Во многих реализациях JVM методы println используют внутреннюю синхронизацию. Вот почему вызов к нему создает забор памяти и делает изменения, сделанные в одном потоке видимыми для другого, устраняя проблему, описанную в 2).

Однако, если вы действительно хотите решить эту проблему, вы должны сделать IntBuffer поточно-безопасным.

+0

Спасибо! Я не понимал, что изменения одного потока могут быть не видны другому. Можете ли вы связать меня с любым ресурсом о том, почему это происходит? – countunique

1

Как минимум вам нужно ключевое слово volatile как на buffer, так и на index. Во-вторых, вам нужно получить доступ к index только один раз под истинным плечом ifs, который у вас есть. Даже после этого вы столкнетесь с ограниченным доступом в 10, вам понадобится больше усилий, чтобы обойти это. Ваш буфер де-факто-стек. Таким образом, даже после всего этого, ваш remove() может работать со устаревшим индексом, поэтому вы будете удалять в середине стека. Вы можете использовать 0 как специальное значение, обозначающее, что слот уже обработан до конца пустым.

Со всем этим я не думаю, что ваш код легко утилизируется. Это в значительной степени требует полной перезаписи с использованием надлежащих средств. Я согласен с @kraskevich:

@StuartHa Naive usually means simple(and most likely inefficent) solution, not an incorrect one.

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