另外运行两个线程的最佳方式?
更新:请参阅此问题的底部以获得完整答案。
我想运行一个辅助线程,以便我的主线程和我的辅助线程交替执行操作(不,我不想在主线程中执行所有操作,它是用于unit testing)。
我提出了两个不同的解决方案,我不知道哪个是最好的,我对第一个问题有疑问:
使用Exchanger
我使用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 exchanger = new Exchanger(); @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émentMATHIE提供了不同的如何实现这一点的例子,在其接受的答案的评论中,请参阅: https : //gist.github.com/cykl/5131021
有三个例子,一个使用CyclicBarrier
,另一个使用Exchanger
,最后一个使用2个Semaphore
。 虽然他说“更具表现力的是基于信号量的信号”是正确的,但我选择使用Exchanger
来简化。 我的unit testing成了:
@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 exchanger = new Exchanger(); @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(); } }
我是否正确,交换方法执行内存同步,就像使用锁一样多?
你是对的。 javadoc指定有一个before-before关系:
“ 内存一致性效果:对于通过Exchanger成功交换对象的每对线程,每个线程中的exchange()之前的操作发生在从另一个线程中相应的exchange()返回之后的操作之前。 ”
您认为什么是最佳解决方案?
两者都是等价的。 你应该以表达力为目标。 我发现基于同步/锁定/监视器的解决方案比基于交换的解决方案更具表现力。 但是,如果您在专用类中抽象此代码并不重要。
我做得对吗,还是我错过了另一个明显的解决方案?
AFAIK否如果您不想重新实施车轮。
请注意,您的基于ReentrantLock的解决方案也可以使用普通旧同步或来自Guava的Monitor编写。
请参阅: http : //docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/util/concurrent/Monitor.html进行比较。
而且我没有发现CyclicBarrier让代码变得更简单……(实际上我并不完全理解如何使用它,但它看起来并不比使用简单
CyclicBarrier不适合您的需求。 它不是为互斥而设计的; 它允许一组线程定义一个公共屏障。 线程将同时执行并在某个时刻等待彼此,然后再转到下一步。
换热器看起来正确 观看http://www.youtube.com/watch?v=WTVooKLLVT8之后,我认为该变量应该是不稳定的,并表示几乎没有任何问题。
锁定机制通过关注锁的互斥来直接解决您在此执行的任务,因此我建议采用该方法。
虽然我还没有使用过Exchanger
,但它看起来像是你想要实现的最简单的解决方案。 代码少于更通用的Lock
/ Condition
版本。 至于记忆的一致性:这就是他们在这里的承诺。