【问题标题】:How to avoid race conditions on alternating synchronous threads?如何避免交替同步线程上的竞争条件?
【发布时间】:2012-10-17 23:33:30
【问题描述】:

我正在努力回忆我以前的 CS 岁月。

一直在尝试以尽可能低的原语正确实现一对同步线程。当然,我应该在生产代码上使用更好的并发工具(可能来自 java.util.concurrency 的东西)。但是,嘿,我这样做是为了挑战。这是我的代码(这是我的第一个问题,所以如果这太长了,请原谅我):

public class Test {

    public volatile Object locker1 = new Object();
    public volatile Object locker2 = new Object();
    public volatile Object locker3 = new Object();

    public class MyRunnable2 implements Runnable {
        public void run() {

            System.out.println( "MyRunnable2 started" );



            synchronized( locker3 ) {
                    try {
                        System.out.println( "r2: waiting for locker3" );
                        locker3.wait();
                        System.out.println( "r2: got locker3" );
                    } catch ( java.lang.InterruptedException e ) {
                        System.out.println( "e: " + e );
                    }
            }



            for ( int c = 0; c < 50; ++c ) {

                synchronized( locker2 ) {

                    try {
                        System.out.println( "r2: waiting for locker2" );
                        locker2.wait();
                        System.out.println( "r2: got locker2" );
                    } catch ( java.lang.InterruptedException e ) {
                        System.out.println( "e: " + e );
                    }
                }

                System.out.println( "r2: " + ( c ) );
                try {
                    Thread.sleep(1);
                } catch ( Exception e ) {
                }

                synchronized( locker1 ) {
                    System.out.println( "r2: signaling locker1" );
                    locker1.notify();
                    System.out.println( "r2: locker1 signaled" );
                }
            }
        }
    }

    public class MyRunnable1 implements Runnable {
        public void run() {
            System.out.println( "MyRunnable1 started" );

            synchronized( locker3 ) {
                    try {
                        System.out.println( "r1: waiting for locker3" );
                        locker3.wait();
                        System.out.println( "r1: got locker3" );
                    } catch ( java.lang.InterruptedException e ) {
                        System.out.println( "e: " + e );
                    }
            }

            for ( int c = 0; c < 50; ++c ) {


                synchronized( locker1 ) {

                    try {
                        System.out.println( "r1: waiting for locker1" );
                        locker1.wait();
                        System.out.println( "r1: got locker1" );
                    } catch ( java.lang.InterruptedException e ) {
                        System.out.println( "e: " + e );
                    }
                }

                System.out.println( "r1: " + ( c ) );
                try {
                    Thread.sleep(1);
                } catch ( Exception e ) {
                }

                synchronized( locker2 ) {
                    System.out.println( "r1: signaling locker2" );
                    locker2.notify();
                    System.out.println( "r1: locker2 signaled" );
                }

            }
        }
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.test();
    }

    public void test() {
        MyRunnable1 r1 = new MyRunnable1();
        MyRunnable2 r2 = new MyRunnable2();
        Thread t1 = new Thread( r1 );
        Thread t2 = new Thread( r2 );
        t1.start();
        t2.start();

        try {
            Thread.sleep(1000);
        } catch ( Exception e ) {

        }
        synchronized( locker3 ) {
            System.out.println( "main: signaling locker3" );
            locker3.notifyAll();
            System.out.println( "main: locker3 signaled" );
        }

        try {
            Thread.sleep(1000);
        } catch ( Exception e ) {

        }

        synchronized( locker1 ) {
            System.out.println( "main: signaling locker1" );
            locker1.notify();
            System.out.println( "main: locker1 signaled" );
        }


        try {
            t1.join();
            t2.join();


        } catch ( java.lang.InterruptedException e ) {
            System.out.println( "e: " + e );
        }
    }
}

我的问题是:如何避免 Test.test() 的竞争条件?大多数情况下,这是可行的——但我对睡眠电话不满意。 另外,请大家评价一下我的风格。我总是乐于自我提升。

编辑:只是为了更清楚。我希望 MyRunnable1 始终先运行。打印一个数字,然后等待 MyRunnable2 打印相同的数字。然后它会打印第二个数字,然后再次等待 MyRunnable2。以此类推。

我想我不能舒适地使用 java.util.concurrency,直到我知道引擎盖下发生了什么。

【问题讨论】:

  • 根据您的旧 CS 时代是多久以前,并发世界可能发生了很大变化。如今,内存和缓存优化做了一些真正奇怪的事情,您尝试访问同步块之外的共享值。查看 Java Concurrency in Practice 以获得关于如何考虑并发问题的非常好的描述。至于这个问题,您要消除的竞争条件是什么?您要强制执行的行为是什么?您希望线程一个接一个地轮流运行吗?
  • 没错,@Superboggly。我希望 MyRunnable1 始终先运行,打印一个数字,然后让位于 MyRunnable2 以打印相同的数字。两个线程总是在同一个地方停下来。编辑问题以使其更清晰。

标签: java multithreading wait synchronized notify


【解决方案1】:

@Chris Thompson 是对的 - 您可以在单个信号对象上交替。但是你永远不会保证哪个线程先走,你必须小心确保你的最后一个线程没有等待你的倒数第二个线程在你的倒数第二个线程已经通知之后完成并退出。

我修改了您的代码以使其正常工作 - 但无法保证谁先执行,然后我还添加了一个替代的“MyRunnableOrdered”来控制两个线程的执行顺序。在任何一种情况下,如果线程没有相同数量的循环要完成,或者任何一个由于错误而退出,那么您将面临饥饿的风险。在后一种情况下,注意并使用被中断的异常会有所帮助。

public class Test {

public Object locker = new Object();
public boolean oneDone = false;

public class MyRunnable2 implements Runnable {
    public void run() {

        System.out.println( "MyRunnable2 started" );

        for ( int c = 0; c < 50; ++c ) {
            synchronized( locker ) {
                System.out.println( "r2: " + ( c ) );
                locker.notify();
                if(c == 49) {
                    oneDone = true;
                }

                try {
                    if(!oneDone) {
                        locker.wait();
                    }
                } catch ( java.lang.InterruptedException e ) {
                    System.out.println( "e: " + e );
                }                    
            }
        }
    }
}

public class MyRunnable1 implements Runnable {
    public void run() {
        System.out.println( "MyRunnable1 started" );

        for ( int c = 0; c < 50; ++c ) {
            synchronized( locker ) {
                System.out.println( "r1: " + ( c ) );
                locker.notify();
                if(c == 49) {
                    oneDone = true;
                }

                try {
                    if(!oneDone) {
                        locker.wait();
                    }
                } catch ( java.lang.InterruptedException e ) {
                    System.out.println( "e: " + e );
                }
            }
        }
    }
}


public Object sequenceLock = new Object();
public boolean sequence = true;

public class MyRunnableOrdered implements Runnable {

    private final boolean _match;

    public MyRunnableOrdered(boolean match) 
    {
        _match = match;
    }

    public void run() {
        System.out.println( "MyRunnable1 started" );

        for ( int c = 0; c < 50; ++c ) {
            synchronized( sequenceLock ) {
                while(_match != sequence) {
                    try {
                        sequenceLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println( "r" + _match + ":" + ( c ) );
                sequence = !sequence;
                sequenceLock.notify();
            }
        }
    }
}    


public static void main(String[] args) {
    Test t = new Test();
    t.test();
}

public void test() {
    MyRunnable1 r1 = new MyRunnable1();
    MyRunnable2 r2 = new MyRunnable2();
    Thread t1 = new Thread( r1 );
    Thread t2 = new Thread( r2 );


    synchronized( locker ) {
        t1.start();
        t2.start();
    }


    try {
        t1.join();
        t2.join();
    } catch ( java.lang.InterruptedException e ) {
        System.out.println( "e: " + e );
    }

    System.out.println("Done part 1");

    MyRunnableOrdered o1 = new MyRunnableOrdered(true);
    MyRunnableOrdered o2 = new MyRunnableOrdered(false);
    synchronized(sequenceLock) {
        sequence = true;
    }
    Thread to1 = new Thread( o1 );
    Thread to2 = new Thread( o2 );
    to1.start();
    to2.start();

    try {
        to1.join();
        to2.join();
    } catch ( java.lang.InterruptedException e ) {
        System.out.println( "e: " + e );
    }       
    System.out.println("Done part 2");
}
}

请注意,MyRunnableOrdered 的想法不会超出两个线程,因为我们无法控制在调用 notify 时唤醒谁。在这种情况下,您想要的是要处理的线程的有序列表。那时并发可能不是最好的解决方案!

如果您决定使用并发库,MyRunnableOrdered 可能还有一个更好的实现,它使用 AtomicBoolean 并且没有锁定。

还要注意,我们不需要使用“volatile”,因为所有变量访问都受到同步块的保护。

【讨论】:

  • 谢谢。您的解决方案看起来非常好!这只是为了弄湿我的脚。我会做更多的实验,然后跳进并发包。我特别喜欢你在 synchronized(locker) 块中启动线程。把你的头绕在并发上并不容易。这需要练习。我希望我最终会达到我能够有这样的想法的地步。
【解决方案2】:

除了睡眠呼叫之外,还有几个基本问​​题(实际上睡眠呼叫本身并没有什么问题......)

对于初学者,您实际上并没有做任何事情来让线程相互发出信号。如果我正确理解你的评论,你想要类似的东西

Thread 1: 1
Thread 2: 1
Thread 1: 2
Thread 2: 2
...

作为输出。这段代码当前要做的是启动两个线程,然后两个线程都将等待。然后主线程将在locker3 对象上调用notifyAll。这意味着等待该对象的任何线程都会运行,但无法保证线程将以什么顺序运行,因为您要通知每个人。有 1 号竞争条件。此外,您只在 locker2locker1 对象上调用 notifyAll 一次,但最终每个线程等待它们大约 50 次。这意味着您的线程将挂起。

你真正需要的是这样的:

  1. 线程 1 和线程 2 已启动
  2. 线程 2 立即等待某个信号对象
  3. 线程 1 立即打印一个数字,然后在同一个信号对象上调用 notify
  4. 线程 1 然后等待同一个信号对象
  5. 线程 2 恢复并打印数字
  6. 线程 2 然后在信号对象上调用 notify
  7. 线程 2 然后等待信号对象
  8. 返回步骤 3。

我不能保证这完全没有竞争条件,但要按照您的建议进行操作,您将需要这样的算法。你也可以让它变得更复杂,但这是一个很好的基线。

【讨论】:

  • 您的建议实际上是我的第一次尝试,但与上面糟糕的解决方案相比,我遇到死锁的频率要高得多。毕竟,我不想在没有充分理由的情况下使事情复杂化;-) 在“我以前的 CS 时代”中,我记得在 C 中使用 pThreads 和 Win32 以您描述的方式使用本机线程 API 实现这一点。这就是我在这个问题中的全部内容=-)
猜你喜欢
  • 2019-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-10
  • 2018-12-24
  • 2015-01-30
  • 2010-09-25
相关资源
最近更新 更多