Лучший способ запустить два потока альтернативно?
Update: см. нижнюю часть этого вопроса для полного ответа.
я хочу запустить вторичный поток, чтобы мой основной поток и мой вторичный поток выполняли операции альтернативно (нет, я не хочу делать все операции в основном потоке, это для модульного теста).
я пришел к двум различным решениям, я не знаю, какой из них лучший, и у меня есть вопросы относительно первого:
используя Обменник
я подошел к чему-то с помощью обменник (пока я не хочу обмениваться только одним объектом).
@Test
public void launchMyTest() {
/**
* An anonymous class to set some variables from a different thread
*/
class ThreadTest extends Thread {
//declare some various attributes that will be set
//NOT DECLARED VOLATILE
...
public final Exchanger<Integer> exchanger = new Exchanger<Integer>();
@Override
public void run() {
try {
//start of the synchronization
int turn = 1;
while (turn != 2) {
turn = this.exchanger.exchange(turn);
}
//do some work and set my various variables
...
//main thread's turn
turn = 1;
this.exchanger.exchange(turn);
//wait for this thread's turn
while (turn != 2) {
turn = this.exchanger.exchange(turn);
}
//redo some other work and reset the various variables
...
//main thread's turn
turn = 1;
this.exchanger.exchange(turn);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
try {
//some work in the main thread
....
//launch the job in the second thread
ThreadTest test = new ThreadTest();
test.start();
//start of the synchronization
int turn = 2;
test.exchanger.exchange(turn);
//wait for this thread's turn
while (turn != 1) {
turn = test.exchanger.exchange(turn);
}
//run some tests using the various variables of the anonymous class
....
//now, relaunch following operations in the second thread
turn = 2;
test.exchanger.exchange(turn);
//wait for this thread's turn
while (turn != 1) {
turn = test.exchanger.exchange(turn);
}
//do some other tests using the various variables of the anonymous class
//...
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
вопрос:
- я прав, что
exchange
метод выполняет синхронизацию памяти, так же, как с помощьюLock
?
Используя Условие
другое решение с помощью условие:
@Test
public void launchMyTest() {
/**
* An anonymous class to set some variables from a different thread
*/
class ThreadTest extends Thread {
//declare some various attributes that will be set
//NOT DECLARED VOLATILE
...
public final Lock lock = new ReentrantLock();
public final Condition oneAtATime = lock.newCondition();
public int turn = 1;
@Override
public void run() {
this.lock.lock();
try {
//do some work and set my various variables
...
//main thread's turn
this.turn = 1;
this.oneAtATime.signal();
//wait for this thread's turn
while (this.turn != 2) {
this.oneAtATime.await();
}
//redo some other work and reset the various variables
...
//main thread's turn
this.turn = 1;
this.oneAtATime.signal();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
this.lock.unlock();
}
}
}
ThreadTest test = new ThreadTest();
test.lock.lock();
try {
//some work in the main thread
....
//launch the job in the second thread
test.turn = 2;
test.start();
//wait for this thread's turn
while (test.turn != 1) {
test.oneAtATime.await();
}
//run some tests using the various variables of the anonymous class
....
//now, relaunch following operations in the second thread
test.turn = 2;
test.oneAtATime.signal();
//wait for this thread's turn
while (test.turn != 1) {
test.oneAtATime.await();
}
//do some other tests using the various variables of the anonymous class
//...
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
test.lock.unlock();
}
}
мне кажется немного больше сложный.
вывод
как вы думаете, что является лучшим решением? Правильно ли я это делаю, или я упускаю еще одно очевидное решение?
я не использовал CountDownLatch
как я хочу бежать несколько операции в качестве альтернативы, и CountDownLatch
не может быть сброшен. И я не нашел что CyclicBarrier
упрощал код... (на самом деле я не совсем понимал, как его использовать, но это не выглядело проще, чем использовать Exchanger
или Condition
)
спасибо.
обновление
@Clément MATHIEU привел различные примеры того, как этого достичь, в комментариях его принято отвечать см.: https://gist.github.com/cykl/5131021
есть три примера, один из которых использует CyclicBarrier
, еще один с помощью Exchanger
, и последний, используя 2 Semaphore
s. Хотя он прав, говоря ,что "более выразительным является семафор", я решил использовать Exchanger
для простоты. Мой модульный тест стал:
@Test
public void launchMyTest() {
/**
* An anonymous class to set some variables from a different thread
*/
class ThreadTest extends Thread {
//declare some various attributes that will be set
//NOT DECLARED VOLATILE
...
public final Exchanger<Integer> exchanger = new Exchanger<Integer>();
@Override
public void run() {
try {
//do some work and set my various variables
...
//main thread's turn
this.exchanger.exchange(null);
//wait for this thread's turn
this.exchanger.exchange(null);
//redo some other work and reset the various variables
...
//main thread's turn
this.exchanger.exchange(null);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
try {
//some work in the main thread
....
//launch the job in the second thread
ThreadTest test = new ThreadTest();
test.start();
//wait for this thread's turn
test.exchanger.exchange(null);
//run some tests using the various variables of the anonymous class
....
//now, relaunch following operations in the second thread
test.exchanger.exchange(null);
//wait for this thread's turn
test.exchanger.exchange(null);
//do some other tests using the various variables of the anonymous class
//...
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
4 ответов
правильно ли, что метод exchange выполняет синхронизацию памяти так же, как и с помощью блокировки?
вы правы. Javadoc указывает, что существует отношение happen-before:
"эффекты согласованности памяти: для каждой пары потоков, которые успешно обмениваются объектами через обменник, действия до exchange() в каждом потоке происходят-до тех, которые следуют за возвратом из соответствующего exchange() в другом нитка."
Как вы думаете, что является лучшим решением?
оба эквивалентны. Вы должны ориентироваться на выразительность. Я нахожу решение на основе синхронизации/блокировки/монитора более выразительным, чем решение на основе обмена. Но на самом деле не имеет значения, абстрагируете ли вы этот код в выделенном классе.
Я делаю это правильно, или я упускаю еще одно очевидное решение?
AFAIK нет, если вы не хотите повторно внедрить колесо.
обратите внимание, что ваше решение на основе ReentrantLock также может быть написано с помощью простой старой синхронизации или монитора из Guava.
обменник выглядит правильно. После просмотра http://www.youtube.com/watch?v=WTVooKLLVT8 я думаю, что переменная должна быть изменчивой, говорит, что вряд ли есть над головой.
механизм блокировки непосредственно решает задачу, которую вы выполняете здесь, сосредоточившись на взаимных исключениях замков, поэтому моя рекомендация для этого подхода.
пока я не использовал Exchanger
тем не менее, похоже, что это самое простое решение для того, чего вы хотите достичь. Меньше кода, чем гораздо более общий Lock
/Condition
версия. Что касается согласованности памяти: это то, что они обещают здесь.