【问题标题】:Understanding Java Thread Interference了解 Java 线程干扰
【发布时间】:2016-10-09 23:22:34
【问题描述】:

我正在学习 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;
    }
}

【问题讨论】:

  • 建议:从运行一个线程开始,看看你是否获得了除0 以外的任何东西——如果没有,那么你的检测方法有问题。
  • 要查看干扰,您必须创建实际的并发执行。您编写的代码在 纳秒 内执行递增或递减,然后休眠一整秒。两个线程以完全相同的纳秒分派的机会微乎其微。您需要编写在紧密循环中同时在多个线程中执行增量和减量数百万次的代码,并在每次迭代后检查意外值。 IE。获取值的线程本地副本,增加/减少副本,增加/减少值,比较。
  • 只要加法和减法是原子操作,Counter 的值将始终等于(# increments) - (# decrements),所以这并不奇怪
  • 但是,如果您在某个特定线程(例如 thread1)中更新计数器,然后尝试输出它的值,它可能会在输出操作之前被另一个线程更新,因此输出将在内部不一致thread1的范围
  • 同上他们所说的(上图):摆脱sleep() 电话。此外,不是让每个线程执行十个操作,而是让每个线程执行十 百万

标签: java multithreading java-threads


【解决方案1】:

要重现,您需要最大化概率同时增加和/或减少计数器(注意:这不是一件容易的事,因为增加/减少计数器是一个非常快的操作),这不是您当前代码的情况,因为:

  1. 在递增/递减计数器之前,您不使用任何机制来同步线程。
  2. 当您知道PrintStream 是线程安全的并且使用内部锁来防止并发访问时,您过于频繁地在错误的位置打印您的消息在标准输出流中,这是一个问题 这降低了同时增加和/或减少计数器的可能性。
  3. 您添加了一个无用的长时间睡眠,这再次降低了同时修改计数器的可能性。
  4. 不要使用尽可能多的线程。

所以,你的代码应该稍微改写一下来解决之前的问题。

要修复 #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++) {
        ...
    }
}

run 方法的for 循环

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);
}

【讨论】:

  • 谢谢,我还没有完全理解CyclicBarrier,但是我实现了你的sn-ps & 可以清楚地看到干扰。
  • 我看不到Counter.await 是如何被调用的,因此在哪里使用了屏障。我尝试在barrier.await();System.out.println("Calling Barrier"); 之前将此行添加到Counter.await,但控制台上没有打印任何内容
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-04
相关资源
最近更新 更多