1

我正在实现一个简单的多线程示例,其中线程 1 每 1000 毫秒打印一次 PING,线程 2 每 2000 毫秒打印一次 PONG。

public class T extends Thread 
{
public void run()
{
int i =10;
while(i>0)
{
    if(Thread.currentThread().getName().equals("T1"))
    {
        System.out.println("\nPING");
        try {Thread.sleep(1000);} 
        catch (InterruptedException e) {e.printStackTrace();}   
    }
    else if (Thread.currentThread().getName().equals("T2"))
    {
        System.out.println("\nPONG");
        try {Thread.sleep(2000);} 
        catch (InterruptedException e) {e.printStackTrace();}
    }
i--;
}
}

public static void main(String[] args) 
{
    Thread t1 = new T();
    Thread t2 = new T();
    t1.setName("T1");
    t2.setName("T2");
    t1.start();
    t2.start();
}
}

但输出就像::

乒乓

乒乓

乒乓

乒乓

乒乓

乒乓

乒乓

乒乓

乒乓

乒乓

但我的预期输出应该是

PING
PING
PONG
PING
PING
PONG
PING
PING
PONG.....

应该对代码进行哪些更改?

4

6 回答 6

4

假设两个线程都在时间 0 开始,并且出于任何(线程调度)原因PONG首先开始。

PONG (now wait 2000) // 0 has elapsed
PING (now wait 1000) // 0 has elapsed
PING (now wait 1000) // 1000 has elapsed
PING (now wait 1000) // 2000 has elapsed
PONG (now wait 2000) // 2000 has elasped
... and so on

如果你想PINGs 和一个 s PONG,考虑使用一些通知机制(信号量、coutdown 锁存器等)。不要依赖sleep()

于 2013-10-16T19:06:00.983 回答
2

Thread.sleep的文档中:

使当前执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度程序的精度和准确性。

如果要确保打印PING的线程运行两次,然后等待另一个线程打印PONG(然后它又等待两个 PIN 再继续),则需要使线程彼此同步。请参阅Object.waitObject.notify

编辑:这是一个同步的版本(它很乱,但问题中的原始版本也是如此;P):

public class T extends Thread
{
    //Static object to synchronize on... bad practice but this is just an example anyway
    static Object synchOnMe = new Object();

    public void run()
    {
        int i = 10;
        while(i > 0)
        {
            if(Thread.currentThread().getName().equals("T1"))
            {               
                System.out.println("PING");
                try
                {
                    Thread.sleep(1000);

                    //Synchronize on every second time (ie. wait for PONG)
                    if((i + 1) % 2 == 0)
                    {
                        synchronized(synchOnMe)
                        {
                            synchOnMe.notify();
                            synchOnMe.wait();
                        }
                    }                   
                }
                catch(InterruptedException e)
                {
                    e.printStackTrace();
                }

            }
            else if(Thread.currentThread().getName().equals("T2"))
            {
                try
                {
                    //Synchronize every time (ie. wait for PING)
                    synchronized(synchOnMe)
                    {
                        synchOnMe.wait();
                        System.out.println("PONG");
                        synchOnMe.notify();
                    }

                    Thread.sleep(2000);
                }
                catch(InterruptedException e)
                {
                    e.printStackTrace();
                }


            }
            i--;
        }

        //One last notify to release the other thread waiting
        synchronized(synchOnMe)
        {
            synchOnMe.notify();
        }

    }

    public static void main(String[] args)
    {
        Thread t1 = new T();
        Thread t2 = new T();
        t1.setName("T1");
        t2.setName("T2");
        t1.start();
        t2.start();
    }
}

输出:

PING
PING
PONG
PING
PING
PONG
PING
PING
PONG
PING
PING
PONG
PING
PING
PONG
于 2013-10-16T19:04:58.477 回答
0

移动

System.out.println("\nPING or PONG");

之后Thread.sleep()不是之前。

或在启动时在踏板之间添加一个 Thread.sleep() 。

t1.start();
Thread.sleep(100);
t2.start();

(但我不喜欢第二种解决方案)

虽然这不是一个专业的解决方案,但它应该可以工作大约 30 次迭代。后来,由于它们没有正确同步,因此不再起作用。

于 2013-10-16T19:10:27.740 回答
0

我会使用较新的并发包中的 ThreadPool:

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool(int)

于 2013-10-16T19:07:10.733 回答
0

首先,您应该使用 for 循环而不是 while 循环。它使您的意图更加清晰。其次,您需要了解线程并不能保证按照您启动它们的顺序执行。所有可以保证的是内核将根据底层操作系统的工作方式开始为线程分配时间片,并且内核将等待最小您为线程指定的睡眠时间(如果内核支持合并线程,有时甚至会更短)。

这意味着 T1 实际上可以在一轮中等待 996 毫秒,而 T2 可以在另一轮中等待 2026 毫秒。例如,当我运行这段代码时,我得到以下输出:
PING
PONG
PING
PING
PONG
PING
PING
PONG
PING
PONG
PING
PING
PONG
PING
PING
PONG
PONG
PONG
PONG
PONG

有点乱。现在,在许多多线程应用程序中,当线程不相互依赖时,这是可以的。但是,如果您希望输出按照您期望的方式排列,那么就像其他答案所建议的那样,您需要使用 wait() 和 notify()。

例如,让 T1 开始,然后让 T1 开始 T2。T2 应在执行时立即等待,然后 T1 应在每次 PING 时通知 T2。然后,T2 可以计算它收到通知的次数,每当计数为偶数时,它就可以输出一个 PONG。

我将实现作为练习留着。

于 2013-10-16T19:11:14.263 回答
0

一般来说,在这种情况下不可能有确定性的输出(除非你使用一些花哨的实时技术)。这是由于操作系统管理线程的方式。线程已经结束 spleeping 的事实并不意味着它会立即获得 CPU 时间。

确定线程执行顺序的唯一方法是在它们之间使用同步。使用谷歌了解更多,因为这是相当复杂的事情。

于 2013-10-16T19:17:52.010 回答