理解Java线程干扰

时间:2016-10-09 23:22:35

标签: java multithreading java-threads

我正在学习Java,来自python背景,并试图理解线程干扰,从代码&本页中的解释:http://docs.oracle.com/javase/tutorial/essential/concurrency/interfere.html

为了重现干扰,我有另一个启动三个线程的类,每个线程随机调用递增或递减10次。

我希望,有3个线程& 30个增量或减量,有些会重叠,因此最终Counter值不等于(# increments) - (# decrements)

但每次运行代码并分析结果输出时,我发现最终值等于(# increments) - (# decrements)。虽然它可能在5次运行后,我不知何故没有受到任何干扰,但更可能是我误解了干扰效应或无意中实现了避免干扰的代码。

这是我的代码:

// file: CounterThreads.java
public class CounterThreads {
    private static class CounterThread implements Runnable {
        private Counter c;

        CounterThread(Counter c)
        {
            this.c = c;
        }

        public void run()
        {
            String threadName = Thread.currentThread().getName();
            for (int i=0; i<10; i++) {
                try {

                    if (((int)(Math.random() * 10) % 2) == 0) {
                        System.out.format("%s - Decrementing...\n", threadName);
                        c.decrement();
                    } else {
                        System.out.format("%s - Incrementing...\n", threadName);
                        c.increment();
                    }
                    System.out.format("%s - The internal counter is at %s\n", threadName, c.value());
                    Thread.sleep(1000);

                } catch (InterruptedException e) {
                    System.out.format("Thread %s interrupted\n", threadName);
                }
            }
        }
    }

    public static void main(String[] args)
    {
        Counter c = new Counter();
        for (int  i=0; i<3; i++) {
            Thread t = new Thread(new CounterThread(c));
            System.out.format("Starting Thread: %s\n", t.getName());
            t.start();
        }
    }
}

文件Counter.java包含从上面的oracle文档复制的代码,为方便起见,此处转载

// file: Counter.java
public class Counter {
    private int c = 0;

    void increment ()
    {
        c++;
    }

    void decrement()
    {
        c--;
    }

    int value()
    {
        return c;
    }
}

1 个答案:

答案 0 :(得分:2)

要重现,您需要最大化概率来增加和/或递减计数器并发(注意:自从递增/递减计数器以来,这不是一项容易的任务是一个非常快的操作),这不是你当前代码的情况,因为:

  1. 在递增/递减计数器之前,您没有使用任何机制来同步线程。
  2. 当您知道PrintStream是线程安全的并且使用内部锁来防止并发访问时,您经常在标准输出流中打印消息并且位置错误/ strong>这会降低同时递增和/或递减计数器的可能性。
  3. 您添加了无用的长睡眠,这再次降低了同时修改计数器的可能性。
  4. 你没有尽可能多地使用线程。
  5. 因此,应该重写您的代码以修复以前的问题。

    要修复#1,您可以使用CyclicBarrier确保所有线程都到达相同的屏障点(位于递增/递减计数器之前),然后再继续操作。

    要修复#2,我建议在增加/减少你的计数器之后只保留一条消息。

    要修复#3,我会删除它,因为它无论如何都是无用的。

    要修复#4,我会使用Runtime.getRuntime().availableProcessors()作为线程使用量,因为它将使用与本地计算机上一样多的处理器,这应该足以完成此类任务。

    因此,最终的代码可以是:

    <强>计数器

    public class Counter {
        private final CyclicBarrier barrier;
        private int c;
    
        public Counter(int threads) {
            this.barrier = new CyclicBarrier(threads);
        }
    
        void await() throws BrokenBarrierException, InterruptedException {
            barrier.await();
        }
        ...
    }
    

    main方法

    public static void main(String[] args) {
        int threads = Runtime.getRuntime().availableProcessors();
        Counter c = new Counter(threads);
        for (int  i=0; i<threads; i++) {
            ...
        }
    }
    

    for方法的run循环

    try {
        // Boolean used to know if the counter has been decremented or not
        // It has been moved before the await to avoid doing anything before
        // incrementing/decrementing the counter
        boolean decrementing = (int)(Math.random() * 10) % 2 == 0;
        // Wait until all threads reach this point
        c.await();
        if (decrementing) {
            c.decrement();
        } else {
            c.increment();
        }
        // Print the message
        System.out.format(
            "%s - The internal counter is at %d %s\n", 
            threadName, c.value(), decrementing ? "Decrementing" : "Incrementing"
        );
    
    } catch (Exception e) {
        System.out.format("Thread %s in error\n", threadName);
    }