0

我试图记住我以前的 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 直到我知道引擎盖下发生了什么。

4

2 回答 2

1

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

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

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

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

你真正需要的是这样的:

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

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

于 2012-10-20T04:08:44.630 回答
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”,因为所有变量访问都受到同步块的保护。

于 2012-10-22T18:04:26.393 回答