8

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

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

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

使用交换器

我想出了一个使用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 {
                //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?

使用条件

使用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();
    }
}

在我看来,这有点复杂。

结论

你认为最好的解决方案是什么?我做得对吗,还是我错过了另一个明显的解决方案?

我没有使用 aCountDownLatch因为我想交替运行多个CountDownLatch操作,并且无法重置。而且我没有发现CyclicBarrier是让代码更简单...(实际上我并没有完全理解如何使用它,但它看起来并不比使用简单Exchangeror Condition

谢谢你。

更新

@Clément MATHIEU 提供了如何实现这一目标的不同示例,在其接受的答案的评论中,请参阅:https ://gist.github.com/cykl/5131021

有三个示例,一个使用 a CyclicBarrier,另一个使用 an Exchanger,最后一个使用 2 Semaphores。虽然他说“更具表现力的是基于信号量的”是正确的,但Exchanger为了简单起见,我选择使用 an 。我的单元测试变成了:

@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

4 回答 4

2

exchanger looks right. After watching http://www.youtube.com/watch?v=WTVooKLLVT8 I think the variable should be volatile, says there is hardly any over head.

于 2013-03-10T20:31:30.770 回答
1

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

你说的对。javadoc 指定有一个发生之前的关系:

内存一致性效果:对于通过 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 不符合您的需求。它不是为互斥而设计的;它允许一组线程定义一个公共屏障。线程将同时执行并在某个时间点相互等待,然后再进行下一步。

于 2013-03-10T19:31:42.153 回答
0

锁定机制通过关注锁的互斥来直接解决您在此处执行的任务,因此我的建议是这种方法。

于 2013-03-10T16:41:21.710 回答
0

虽然我还没有使用过Exchanger,但它看起来是您想要实现的最简单的解决方案。比更通用的Lock/Condition版本更少的代码。至于内存一致性:这就是他们在这里承诺的。

于 2013-03-10T16:58:21.537 回答