2015-08-04 2 views
0

Я пытался решить проблему, связанную с list.add и list.remove с комбинацией в использовании Thread in java. Давайте предположим, что мы будем играть с StackArrayList <object> манипуляции с использованием Thread in Java

это мой Stack класс определение ..

import java.util.ArrayList; 

public class Stack { 

    private int size; 
    private int maxSize; 

    private final ArrayList<Object> list; 

    public Stack(int size) { 
     this.size = 0; 
     this.maxSize = size; 
     this.list = new ArrayList<Object>(size); 
    } 

    public boolean push(Object o) { 
     if (size >= maxSize) { 
      return false; 
     } 

     this.list.add(0, o); 
     this.size++; 
     return true; 
    } 

    public Object pop() { 
     Object o; 
     if (this.size == 0) { 
      return null; 
     } 

     o = this.list.remove(0); 
     this.size--; 
     return o; 
    } 

    public int size() { 
     return this.size; 
    } 
} 

А вот как мы с использованием стека в потоке в Java

final Stack stack = new Stack(4); 
for(int i = 0; i < 10000; i++) { 
    final String data = "hello " + i; 
    final int x = i; 
    new Thread(new Runnable() { 
     public void run() { 
      if(x % 2 == 0) { 
       System.out.println(stack.push(data)); 
      } else { 
       System.out.println(stack.pop()); 
      } 
     } 
    }).start(); 
} 

Поэтому в основном мы просто создать 10000 для управления объектом Stack. stack.push привело True (если стек еще не полностью) или ложным (если стек уже заполнен) stack.pop привела нуль, если стек пуст

И вопрос: Что случилось с реализацией стека выше, и как это исправить?

Мой анализ до сих пор, как поток работает в java. Поток запускается параллельно, а не последовательно. Я попытался выполнить программу, и иногда вызывается исключение IndexOutOfBounds. Если мой анализ верен (или закрыт), есть ли способ избежать исключения? Может быть, есть некоторый метод проверки в классе Stack?

Если мой анализ является ложным, то что не так с реализацией выше? и как это исправить?

+1

Реализация стека не является потокобезопасной. Безопасность потоков - это защита изменчивого состояния, чтобы объекты подвергались последовательным, надежным переходам состояния при доступе одновременно. К сожалению, ваш класс подвержен условиям гонки, которые оставят ваши объекты неопределенным или непоследовательным состоянием. Использование блокировки для принудительного взаимного исключения потоков - это простой (если не исполнительный) способ обеспечения перехода атомарного состояния. – scottb

ответ

4

Что случились с реализацией стеки выше

Вашей реализации хорош, когда только один поток может получить доступ к объекту стеки. Но если по крайней мере 2 потока могут выполнять операции pop и push в одном стеке, то может возникнуть data races. Основное описание многопоточности java описано в JSR-133.

Представьте ситуацию с этим кодом от pop метода:

if (this.size == 0) { 
    return null; 
} 
o = this.list.remove(0); 
  1. Первый поток выполняется, если условие, размер 1.
  2. Второй поток выполняет то же самое, если условие - размер по-прежнему 1.

  3. Первая нить пытается удалить элемент с индексом 0 из списка - успех, размер становится 0.

  4. Второй поток пытается удалить элемент с индексом 0 из списка, когда размер 0 - исключение.

Вы должны быть уверены, что некоторые события произошли до другие. Один из способов - синхронизировать ваши методы pop и push. Это можно сделать легко, добавив ключевое слово synchronized перед возвратом метода.

public synchronized boolean push(Object o) {...} 

public synchronized Object pop() { ...} 

Здесь оба метода synchronized на одном объекте - this.Поэтому, когда один поток получает this блокировку, выполнив pop или push, ни один другой поток не может ввести блок кода или метод заблокирован (синхронизирован) тем же this объектом. Эти методы полностью безопасны для потоков.

Если вы используете некоторую синхронизированную коллекцию вместо обычного ArrayList, вы все равно можете попасть в неприятности, потому что ваша логика зависит от size переменной и предыдущего сценария ошибок. Если вам нужна параллельная реализация Stack, вы можете использовать класс LinkedBlockingDeque от java.util.concurrent. Это также было бы намного более эффективно, потому что стоимость добавления элемента в начало ArrayList очень высока. Но если вы хотите самим имитировать его, вы можете просто добавить синхронизированные модификаторы в методы pop и push и изменить свой список на LinkedList.

+0

Я буду копировать то, что другой пользователь по имени erhun сказал в другой теме вопроса. «ну, но что, если у вас есть два метода: addFinisher и delFinisher? Оба метода являются потокобезопасными, но поскольку оба доступа к тому же ArrayList, вы все равно получите проблемы». Как вы можете видеть, мой стек также имеет в нем 2 метода. Любое предложение об этом? Большое спасибо –

+0

Редактировать: также как об использовании syncronizedlist или lock в этой ситуации? Большое спасибо –

+0

@ Ship.Asriadie, обновите ответ. – ka4eli

1

@ ka4eli сказал вам, почему ваш класс Stack не поточно-, но это тоже неправильно:

if(x % 2 == 0) { 
    System.out.println(stack.push(data)); 
} else { 
    System.out.println(stack.pop()); 
} 

Даже если ваш стек полностью потокобезопасной, вы обязаны получить NullPointerExceptions в другой случай.

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

+0

Но те блокируют доступ к одному и тому же объекту Stack, не так ли? Поэтому, если поток 1 запускает блок else (pop), поток будет обращаться к объекту стека и пытается использовать его ресурсы, но поскольку поток 0 использует его в синхронизированном режиме, поэтому поток 1 должен ждать, пока поток 0 не завершит свою работу, вправо ? –

+0

@ Ship.Asriadie, No, Посмотрите на реализацию OP: 'stack.pop()' возвращает 'null', если стек пуст. –