2014-08-30 3 views
1
public class TestConcurrentForList { 

List<Integer> mainList = new ArrayList<Integer>(); 
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); 
Random r = new Random(); 

public void start() throws InterruptedException { 
    Runnable cmd = new Runnable() { 
     @Override 
     public void run() { 
      List<Integer> tempList = mainList; 
      mainList = new ArrayList<Integer>(); 
      for (Integer i: tempList) { 
       System.out.println("subThread:" + i); 
      } 
     } 
    }; 
    scheduledExecutorService.scheduleAtFixedRate(cmd, 1, 1, TimeUnit.MILLISECONDS); 
    while (true) { 
     mainList.add(r.nextInt(200)); 
     Thread.sleep(100); 
    } 
} 

public static void main(String[] args) { 
    TestConcurrentForList tester = new TestConcurrentForList(); 
    try { 
     tester.start(); 
    } catch (Exception e) { 
     e.printStackTrace(); 
     System.err.println(e.getMessage()); 
    } 
} 

}Является ли этот фрагмент кода Java безопасным?

Часть нашего кода продукта любит это, основной поток и подтему разделяют mainList. Я запускаю время тестового серваля, но никогда не воспроизвожу исключение ConcurrentModificationException.

обновление:

спасибо за все ваши ответив, этот код на самом деле является краткой абстракцией нашего производства кода. То, что я хочу сделать на самом деле, очень просто:

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

Может быть, более безопасным способом для достижения этой цели является извлечение

List<Integer> tempList = mainList; 
mainList = new ArrayList<Integer>(); 

к основному потоку, и передать TempList к югу от нити. Код, который я перечисляю ранее, является устаревшим кодом, и я хочу исправить этот код.

+1

Вы должны определенно объявить 'mainList'' volatile'; в противном случае возможно, что это не будет делать то, что вы хотите. –

+0

@DavidWallace Чтобы быть понятным, ключевое слово 'volatile' используется, чтобы указать, что значение переменной будет изменено различными потоками. Создание переменной 'volatile' не влияет на то, можно ли считать код« потокобезопасным ». –

+0

Это не потокобезопасность, потому что, когда у вас есть несколько потоков, у вас будет 'ConcurrentModificationException' на' mainList.add (r.nextInt (200)) '. –

ответ

0

Нет, это не безопасный поток. Вы не используете какие-либо средства синхронизации около mainList. Тот факт, что код не выбрасывает ConcurrentModificationException, не означает, что код является потокобезопасным. Это просто означает, что вы может есть состояние гонки если брошен.

+0

Я не думаю, что это совершенно правильно. Только один поток работает с 'mainList' в любой момент, поэтому нет необходимости его синхронизировать. Единственное, что мешает этому быть потокобезопасным, - это то, что 'mainList' не объявлен как' volatile'. В этом случае вам действительно не нужен поточно-безопасный список. –

+1

@DavidWallace: «средства синхронизации» включают 'volatile'. Кроме того, даже если mainList объявлен как изменчивый, поскольку «переключатель» ссылки состоит из двух операций, один и тот же список массивов может управляться более чем одним потоком и, следовательно, не является потокобезопасным. –

+0

«средства синхронизации» относятся в основном к вещам, которые могут использоваться для синхронизации операций и включают в себя такие вещи, как CAS. –

0

Нет, я не думаю, что код является потокобезопасным, потому что основной поток может вызвать List.add, когда поток пула назначает новое значение mainList. Если mainList, в котором используется примитив, может быть достаточно, чтобы сделать его «изменчивым». Но я не думаю, что вы можете использовать «volatile» с объектными ссылками.

Чтобы сделать задание безопасным, вам нужно будет синхронизировать то время как вы делаете назначение, а затем и везде, где вы пытаетесь коснуться mainList как:

Object lock = new Object(); 
    ... 
     synchronized (lock) { 
      mainList = new ArrayList<Integer>(); 
     } 
    ... 
     synchronized (lock) { 
      mainList.add(r.nextInt(200)); 
     } 

Это обеспечит, что пул потоков не может переназначить mainList, когда основной поток находился в процессе вызова add().

Но я не уверен, что вы можете получить исключение ConcurrentModificationException, если только основной поток изменяет список, а поток пула выполняет только итерацию через элементы. И даже если поток пула изменил список, я все еще не уверен, если вы можете получить CME, если поток пула изменил новый список, который еще не был назначен для mainList.

Итак, если вы видите CME, я подозреваю, что ваш тест на самом деле не отражает то, что происходит на производстве.

2

Как указывает Дэвид Уоллес, вам необходимо хотя бы объявить mainList как volatile.

Однако это само по себе не делает код безопасным для потоков. Даже несмотря на то, что вы переключаете ссылку в потоке cmd, основной поток, возможно, уже вытащил ссылку до того, как это произойдет, и затем может продолжить работу над ней в то же самое время, когда поток cmd считывается из нее.

Например, это возможная последовательность событий:

  1. cmd нить получает mainList ссылки и получает список
  2. Основного потока получает mainList ссылки, а также получает список
  3. cmd нить создает новый список, B и присваивает его mainList
  4. Основная нить начинает подсчет случайного числа
  5. cmd поток начинает итерация в список А
  6. Основной поток добавляет его случайное число перечислить
  7. cmd поток продолжает перебирать изменение списка, в настоящее время в неустойчивом состоянии из-за одновременной модификации

EDIT: В этот момент я планировал изменить предложение, чтобы сделать то, что вы хотели, но я понял, что может быть несколько совершенно разных вещей, которые вы хотели сделать с этим кодом, поэтому одно предложение было бы просто предположением , Если вы хотите получить решение, я предлагаю вам начать новый вопрос, описывающий вашу цель более подробно.

+0

Когда вы говорите: «Основной поток извлекает ссылку« mainList' », вы имеете в виду, что копирует ссылку на стек при подготовке вызова метода? –

+0

@DavidWallace: Действительно. В какой-то момент он должен быть извлечен из памяти в регистр (или, чтобы говорить в байт-коде JVM, точка, где ['getfield'] (http://docs.oracle.com/javase/specs/jvms/se7/ html/jvms-6.html # jvms-6.5.getfield). Происходит ли это до или после того, как случайное число было рассчитано, зависит от компилятора, но в любом случае есть временное окно, в котором оно было скопировано из памяти. – Dolda2000