Java多线程查询

3

你好,我是并发编程领域的新手。我在测试以下代码时发现线程中的while循环似乎没有终止。请问有人能帮忙解释一下这里发生了什么。

public class PrimePrinter{
    public long counter = 0;
    public synchronized long getCounter() {
        return counter++;
    }
    public Thread makeThread() {
        Runnable rn = new Runnable() {          
            /* (non-Javadoc)
             * @see java.lang.Runnable#run()
             */
            @Override
            public void run() {
                while (counter < 100) {
                    try {
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + " : " +getCounter());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }                   
                }
            }
        };
        return new Thread(rn);
    }
    public static void main(String[] args) {
        int n = 10;
        Thread[] threads = new Thread[10];
        PrimePrinter pp = new PrimePrinter();
        for(int i = 1; i < n; i++) {            
            threads[i] = pp.makeThread();
            threads[i].start();
        }
    }
}

输出的最后几行
Thread-4 : 81
Thread-5 : 87
Thread-7 : 91
Thread-5 : 97
Thread-2 : 95
Thread-4 : 98
Thread-6 : 96
Thread-8 : 90
Thread-1 : 93
Thread-3 : 92
Thread-0 : 94
Thread-2 : 99
Thread-6 : 107
Thread-3 : 103
Thread-0 : 104
Thread-1 : 105
Thread-8 : 106
Thread-5 : 102
Thread-4 : 101
Thread-7 : 100

2
似乎线程的while循环没有终止 - 你为什么这样想?你的输出难道不表明while循环已经终止了吗? - eis
这是因为您的线程都在执行while循环,但只有一个线程在增加计数器。这些行在所有线程都没有达到终止条件之前被打印出来。 - Tim
可能是因为 while (counter < 100) { 没有同步,所以所有线程都将计数器与100进行比较,当它为99时,它们都有机会再运行一次。尝试使用 AtomicLong 替代 long 变量或在循环检查中调用 getCounter() - Vlad Topala
2个回答

4
这段代码之所以不起作用,有一个关键原因必须注意。请考虑您的代码实际运行方式。它会检查计数器的值,然后休眠,然后打印,最后增加。

您有9个线程同时运行。然后,其中8个线程检查值,并且此时值小于100。因此它们通过测试并继续执行。所有这8个线程都会休眠1000毫秒。同时,您的另一个线程刚刚增加了该值...还有8个线程即将从休眠中醒来。

因此,如果您的值为99(它不可避免地会是),则刚刚增加的线程现在将给出值为100。然后,那些已经通过测试的8个线程也将进行增加,使计数器的结果值为108。

但是,最后一个运行的线程将显示107的值,因为您对所有运行程序都打印了结果然后才进行增加操作。

如果您将此过程反过来(如下面的代码所示),就不会遇到这个问题。

以下代码可以正确运行:

public class PrimePrinter {
    public long counter = 0;

    public synchronized long getCounter() {
        return counter;
    }

    public synchronized void incrementCounter() {
        counter++;
    }

    public Thread makeThread() {
        Runnable rn = new Runnable() {
            /*
             * (non-Javadoc)
             * 
             * @see java.lang.Runnable#run()
             */
            @Override
            public void run() {
                while (getCounter() < 100) {
                    try {
                        incrementCounter();
                        System.out.println(Thread.currentThread().getName()
                                + " : " + getCounter());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        return new Thread(rn);
    }

    public static void main(String[] args) {
        int n = 10;
        Thread[] threads = new Thread[10];
        PrimePrinter pp = new PrimePrinter();
        for (int i = 1; i < n; i++) {
            threads[i] = pp.makeThread();
            threads[i].start();
        }
    }
}

嗨Saleh,感谢您的见解。现在我更好地理解了问题。但是我认为增量和打印应该合并成一个同步块。因为当我执行您的代码时,我得到以下输出。Thread-4 : 81 Thread-6 : 81 Thread-1 : 81 Thread-6 : 90 Thread-0 : 90 Thread-5 : 90 Thread-7 : 90 Thread-1 : 90 Thread-4 : 90 Thread-8 : 90 Thread-2 : 90 Thread-3 : 90 Thread-5 : 99 Thread-2 : 99 Thread-3 : 99 Thread-7 : 99 Thread-1 : 99 Thread-0 : 99 Thread-4 : 99 Thread-8 : 99 Thread-6 : 99 Thread-5 : 100 - sathish j
它仍然可以打印高于100的值。 - Maurice Perry
是的,@MauricePerry,虽然可能性极小,但它确实可能发生,因为计数器检查和递增的速度非常快。此外,Sathish,我不知道你想要这个。我已经相应地修改了代码。 - Sal

2
每个线程在测试 counter < 100 后等待一秒钟,这一秒钟内,其他线程可以增加计数器。
更新:你可以像这样做:
            while (true) {
                long current = getCounter();
                if (current >= 100) {
                    break;
                }
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " : " + current);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }                   
            }

感谢Maurice的见解,你的代码完美地运行了。 - sathish j

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接