如何避免交替同步线程的竞争条件?

时间:2012-10-17 23:33:30

标签: java multithreading wait synchronized notify

我正在努力记住我过去的旧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,直到我知道幕后发生了什么。

2 个答案:

答案 0 :(得分:1)

除了睡眠呼叫之外,还有一些基本问题(实际上睡眠呼叫没有任何内在错误......)

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

的内容
Thread 1: 1
Thread 2: 1
Thread 1: 2
Thread 2: 2
...

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

你真正需要的是这样的:

  1. 启动线程1和线程2
  2. 线程2立即等待一些信号对象
  3. 线程1立即打印一个数字,然后在同一个信号对象上调用notify
  4. 线程1然后等待相同的信号对象
  5. 线程2恢复并打印数字
  6. 线程2然后调用信号对象上的通知
  7. 线程2然后等待信号对象
  8. 返回第3步。
  9. 我不能保证完全没有竞争条件,但要做你所建议的,你需要一个这样的算法。你也可以使它任意复杂化,但这是一个很好的基线。

答案 1 :(得分: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时唤醒谁。在这种情况下,您需要的是一个有序的线程列表。那时并发可能不是最好的解决方案!

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

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