用于模拟Java线程中的竞争条件的代码

mvg*_*mvg 5 java multithreading

我是Java多线程的新手.我正在学习种族条件的概念.

基于Oracle文档

http://docs.oracle.com/javase/tutorial/essential/concurrency/interfere.html

我创建了一个示例代码,如下所示

public class CounterTest {

    public static void main(String[] args) {

        Thread thread1 = new Thread(new CounterIncThread());
        thread1.setName("add thread");
        thread1.start();

        Thread thread2 = new Thread(new CounterDecThread());
        thread2.setName("sub thread");
        thread2.start();

        Thread thread3 = new Thread(new CounterIncThread());
        thread3.setName("add thread2");
        thread3.start();
    }

}


class CounterIncThread implements Runnable {
    public void run() {
        SynchronizedCounter counter = new SynchronizedCounter();
        counter.increment();
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String threadName =
                Thread.currentThread().getName();
        System.out.println(threadName+ ": "+counter.value());
    }
}

class CounterDecThread implements Runnable {
    public void run() {
        SynchronizedCounter counter = new SynchronizedCounter();
        counter.decrement();
        String threadName =
                Thread.currentThread().getName();
        System.out.println(threadName+ ": "+counter.value());
    }
}

class SynchronizedCounter {
    private int c = 0;

    public  void increment() {
        c++;
    }

    public   void decrement() {
        c--;
    }

    public  int value() {
        return c;
    }

}
Run Code Online (Sandbox Code Playgroud)

该代码不显示任何比赛条件.你能帮助我,如何使用上面的代码刺激竞争条件?

谢谢

Chr*_*s K 7

为了在两个线程之间进行竞争,这两个线程之间必须存在共享状态,并且该状态的交互(读取和写入)必须在mutualy独占块(也称为同步)之外发生.读取,递增然后写回同步块之外的易失性字段就是一个很好的例子.

例如,请考虑此博客中记录的这种情况.

线程A和B都可以在发生任何修改之前读取计数器.然后他们都增加,然后他们都写.结果将是18,而不是19.因为它已经是19,我们将需要线程B来读取线程A写入计数器后的计数器.哪个,有时可能发生.这就是它被称为种族的原因.

在此输入图像描述

为了可靠地实现这种竞争,请更改上面的测试代码以在线程外部创建计数器,然后通过其构造函数将其传递给它们.

你遇到的第二个问题是重叠操作的窗口是非常好的,并且考虑到启动一个线程,相比之下,很多头部比较,那么这三个线程在恰当的时间内重叠的可能性非常大低.因此,为了增加他们的几率,你应该在一个紧密的循环中重复运行.

以下代码演示了上述两个概念.所做的更改是:

  1. 重命名类使其使用更清晰
  2. 在两个线程之间共享MyCounter的状态
  3. 每个线程内的紧密循环,调用增量1,000,000次
  4. 主线程现在阻止使用join()等待两个线程完成,这将取代之前的Thread.sleep
  5. MyCounter中的计数器值c现在是不稳定的; 这告诉JVM总是向共享内存寻找值,而不是通过在遇到之间将它保持在寄存器内来进行优化.为了让比赛更糟糕,请关注挥发性,看看会发生什么:)
  6. 主循环然后通过打印出计数器的值来完成,该值应为2,000,000.但这不会是由于在不稳定的柜台上进行的比赛.

.

public class CounterTest {    
    public static void main(String[] args) throws InterruptedException {   
        MyCounter counter = new MyCounter();

        Thread thread1 = new Thread(new CounterIncRunnable(counter));
        thread1.setName("add thread");
        thread1.start();

        Thread thread2 = new Thread(new CounterIncRunnable(counter));
        thread2.setName("add thread2");
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println(counter.value());
    }    
}


class CounterIncRunnable implements Runnable {
    private MyCounter counter;

    public CounterIncRunnable(MyCounter counter) {
        this.counter = counter;
    }

    public void run() {
        for ( int i=0; i<1000000; i++ ) {
            counter.increment();
        }
    }
}


class MyCounter {
    private volatile int c = 0;

    public  void increment() {
        c++;
    }

    public   void decrement() {
        c--;
    }

    public  int value() {
        return c;
    }    
}
Run Code Online (Sandbox Code Playgroud)

最后,只是为了好玩; 将synchronized同步添加到MyCounter的increment方法,然后重新运行.竞争条件将消失,现在程序将正确打印2000000.这是因为每次增加调用现在只允许一个线程一次进入共享方法.因此序列化对共享变量c的每次访问,并结束竞争.


djn*_*jna 6

最简单的竞争条件是两个线程使用此模式更新某些共享数据

  read a value
  think for a bit, giving another thread a chance to get in
  increment the value and write it back
Run Code Online (Sandbox Code Playgroud)

所以现在如果你有两个线程在运行,每个线程递增一个初始值为43的计数器,我们期望这样

  A reads value 43
  A thinks
  A increments and writes 44
  B reads value 44
  B thinks
  B increments and writes 45
Run Code Online (Sandbox Code Playgroud)

但这可能发生,因为"思考窗口"

  A reads value 43
  A thinks
  B reads value (it's still) 43
  B thinks
  B increments 43 to 44 and writes
  A increments 43 to 44 and write
  // the value is now 44, and we expected it to be 45
Run Code Online (Sandbox Code Playgroud)

竞赛的关键思想是你会得到意想不到的不良影响,例如在库存应用程序中,两个线程各自减少库存量,就像上面的例子中我们"丢失"其中一个减量.

现在您的代码有两个问题:

1).没有共享值,所以我们没有机会看到任何这样的争论

2).你在一行代码中递增一个整数,因此两个线程碰撞的可能性很小.在模拟比赛中,如上所示,最好将读写分开,然后通过睡眠创建一个"机会之窗"来模拟思考时间.在多处理器环境中,线程可能真正并行运行,即使单行代码可能会出现竞争,因为JVM内部会进行读写操作,甚至可能保留值的缓存.