交替运行两个线程的最佳方式? [英] Best way of running two threads alternatively?

查看:92
本文介绍了交替运行两个线程的最佳方式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新:请参阅此问题底部的完整答案。



我想运行辅助线程,和我的辅助线程交替执行操作(不,我不想做所有的操作在主线程,它是一个单元测试)。



我想出了两个不同的解决方案,我不知道哪个是最好的,我有关于第一个问题:



使用交换器



我使用 Exchanger (而我不想只交换一个对象)。

  @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>接受答案,请参阅: https://gist.github.com/cykl/5131021



有三个例子,一个使用 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();
}
}


解决方案


我是否正确的交换方法执行内存同步,就像使用Lock一样?


对。 javadoc指定有一个happens-before关系:



内存一致性效果:对于通过交换器成功交换对象的每对线程,


()在每个线程中的exchange()

您认为最好的解决方案是什么?


两者都是等效的。你应该瞄准表现力。我发现基于同步/锁/监视器的解决方案比基于交换的解决方案更具表达性。但是,如果你在一个专门的类中抽象这个代码,这并不重要。


我做的是正确的,解决方案?


AFAIK否如果您不想重新实现车轮。



请注意,您的基于ReentrantLock的解决方案也可以使用普通的旧同步或来自Guava的Monitor进行编写。



查看: http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/util/concurrent/Monitor.html 进行比较。


我没有发现CyclicBarrier使代码更简单...(实际上我并不完全了解如何使用它,但它看起来不像使用

简单

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 a Lock?

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 Semaphores. 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();
    }
}

解决方案

Am I correct that the exchange method performs memory synchronization, just as much as using a Lock?

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."

What do you think is the best solution?

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.

Am I doing it right, or do I miss another obvious solution?

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.

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

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.

这篇关于交替运行两个线程的最佳方式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆