问题描述
更新:请参阅此问题底部的完整答案。
我想运行辅助线程,和我的辅助线程交替执行操作(不,我不想做所有的操作在主线程,它是一个单元测试)。
我想出了两个不同的解决方案,我不知道哪个是最好的,我有关于第一个问题:
使用交换器
我使用(而我不想只交换一个对象)。
@Test
public void launchMyTest(){
/ **
*一个匿名类从不同的线程设置一些变量
* /
类ThreadTest extends Thread {
//声明一些将要设置的各种属性
//未声明的挥发
...
public final Exchanger< Integer> exchange = new Exchanger< Integer>();
@Override
public void run(){
try {
//同步开始
int turn = 1;
while(turn!= 2){
turn = this.exchanger.exchange(turn);
}
//做一些工作并设置我的各个变量
...
//主线程的回合
turn = 1 ;
this.exchanger.exchange(turn);
//等待这个线程的回合
while(turn!= 2){
turn = this.exchanger.exchange(turn);
}
//重做一些其他工作并重置各种变量
...
//主线程转
turn = 1;
this.exchanger.exchange(turn);
} catch(InterruptedException e){
Thread.currentThread()。interrupt();
}
}
}
try {
//在主线程中工作
.... b
$ b //在第二个线程中启动作业
ThreadTest test = new ThreadTest();
test.start();
//同步开始
int turn = 2;
test.exchanger.exchange(turn);
//等待这个线程的回合
while(turn!= 1){
turn = test.exchanger.exchange(turn);
}
//使用匿名类的各种变量运行一些测试
....
//现在,重新启动以下操作第二个线程
turn = 2;
test.exchanger.exchange(turn);
//等待这个线程的回合
while(turn!= 1){
turn = test.exchanger.exchange(turn);
}
//使用匿名类的各种变量进行其他测试
// ...
} catch(InterruptedException e) {
Thread.currentThread()。interrupt();
}
}
问题:
b $ b
- 我更正了
exchange
方法执行内存同步,就像使用Lock
?
使用条件
使用:
@Test
public void launchMyTest(){
/ **
*匿名类从不同的线程设置一些变量
* /
类ThreadTest extends Thread {
//声明一些将被设置的各种属性
//未声明的挥发
...
public final Lock lock = new ReentrantLock();
public final Condition oneAtATime = lock.newCondition();
public int turn = 1;
@Override
public void run(){
this.lock.lock();
try {
//做一些工作,并设置我的各种变量
...
//主线程的回合
this.turn = 1;
this.oneAtATime.signal();
//等待这个线程的回合
while(this.turn!= 2){
this.oneAtATime.await();
}
//重做一些其他工作,并重置各种变量
...
//主线程的回合
这个。 turn = 1;
this.oneAtATime.signal();
} catch(InterruptedException e){
Thread.currentThread()。interrupt();
} finally {
this.lock.unlock();
}
}
}
ThreadTest test = new ThreadTest();
test.lock.lock();
try {
//在主线程中工作
....
//在第二个线程中启动作业
test.turn = 2;
test.start();
//等待这个线程的回合
while(test.turn!= 1){
test.oneAtATime.await();
}
//使用匿名类的各种变量运行一些测试
....
//现在,重新启动以下操作第二个线程
test.turn = 2;
test.oneAtATime.signal();
//等待这个线程的回合
while(test.turn!= 1){
test.oneAtATime.await();
}
//使用匿名类的各种变量进行其他测试
// ...
} catch(InterruptedException e) {
Thread.currentThread()。interrupt();
} finally {
test.lock.unlock();
}
}
在我看来有点复杂。 p>
结论
您认为最好的解决方案是什么?
我没有使用 CountDownLatch
作为我想要运行多个操作, CountDownLatch
无法重置。我没有发现 CyclicBarrier
使代码更简单...(实际上我不完全明白如何使用它,但它看起来不简单比使用 Exchanger
或条件
)
更新
@ClémentMATHIEU提供了不同的示例, =http://stackoverflow.com/a/15326551/1768736>接受答案,请参阅:
有三个例子,一个使用 CyclicBarrier
,另一个使用
Exchanger
,最后一个使用2 Semaphore
。虽然他是对的说,更表达是基于信号量为基础的,我选择使用 Exchanger
为简单。我的单元测试成为:
@Test
public void launchMyTest(){
/ **
*一个匿名类从不同的线程设置一些变量
* /
类ThreadTest extends Thread {
//声明一些将要设置的各种属性
// NOT DECLARED VOLATILE
...
public final Exchanger< Integer> exchange = new Exchanger< Integer>();
@Override
public void run(){
try {
//做一些工作并设置我的各个变量
...
//主线程的回合
this.exchanger.exchange(null);
//等待此线程的回合
this.exchanger.exchange(null);
//重做一些其他工作并重置各种变量
...
//主线程的回合
this.exchanger.exchange(null );
} catch(InterruptedException e){
Thread.currentThread()。interrupt();
}
}
}
try {
//在主线程中工作
.... b
$ b //在第二个线程中启动作业
ThreadTest test = new ThreadTest();
test.start();
//等待这个线程的回合
test.exchanger.exchange(null);
//使用匿名类的各个变量运行一些测试
....
//现在,在第二个线程中重新启动以下操作
test.exchanger.exchange(null);
//等待这个线程的回合
test.exchanger.exchange(null);
//使用匿名类的各种变量进行其他测试
// ...
} catch(InterruptedException e){
Thread.currentThread()。interrupt();
}
}
对。 javadoc指定有一个happens-before关系:
内存一致性效果:对于通过交换器成功交换对象的每对线程,
两者都是等效的。你应该瞄准表现力。我发现基于同步/锁/监视器的解决方案比基于交换的解决方案更具表达性。但是,如果你在一个专门的类中抽象这个代码,这并不重要。
AFAIK否如果您不想重新实现车轮。
请注意,您的基于ReentrantLock的解决方案也可以使用普通的旧同步或来自Guava的Monitor进行编写。
查看:进行比较。
CyclicBarrier不适合你的需要。允许一组线程定义一个共同的障碍线程将同时执行并在移动到下一步之前的某个时刻等待彼此。
Update: see bottom of this question for a complete answer.
I want to run a secondary thread, so that my main thread and my secondary thread perform operations alternatively (no, I don't want to do all the operations in the main thread, it is for a unit test).
I came up to two different solutions, I don't know which is the best, and I have questions regarding the first one:
Using Exchanger
I came up to something using an Exchanger (while I don't want to exchange only one object).
@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();
}
}
Question:
- Am I correct that the
exchange
method performs memory synchronization, just as much as using aLock
?
Using Condition
Another solution using a Condition:
@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();
}
}
It seems to me a bit more complicated.
Conclusion
What do you think is the best solution? Am I doing it right, or do I miss another obvious solution?
I didn't use a CountDownLatch
as I want to run several operations alternatively, and the CountDownLatch
cannot be reset. And I didn't find that the CyclicBarrier
was making the code simpler... (actually I didn't totally understand how to use it, but it didn't look simpler than using Exchanger
or Condition
)
Thank you.
Update
@Clément MATHIEU provided different examples of how to achieve this, in comments of its accepted answer, see: https://gist.github.com/cykl/5131021
There are three examples, one using a CyclicBarrier
, another one using an Exchanger
, and a last one using 2 Semaphore
s. While he is right to say that the "more expressive is the semaphore based one", I chose to use an Exchanger
for simplicity. My unit test became:
@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();
}
}
You are right. The javadoc specifies there is a happen-before relation:
"Memory consistency effects: For each pair of threads that successfully exchange objects via an Exchanger, actions prior to the exchange() in each thread happen-before those subsequent to a return from the corresponding exchange() in the other thread."
Both are equivalent. You should target expressiveness. I find the synchronisation/Lock/Monitor based solution more expressive than the exchanged based one. But it does not really matter if you abstract this code in a dedicated class.
AFAIK no If you don't want to re-implement the wheel.
Please note that your ReentrantLock based solution can also be written using plain old synchronisation or Monitor from Guava.
See: http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/util/concurrent/Monitor.html for a comparison.
CyclicBarrier does not fit your needs. It is not designed for mutual exclusion; it allows a set of threads to define a common barrier. Threads will execute concurrently and wait for each other at some point before moving to the next step.
这篇关于交替运行两个线程的最佳方式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!