繁体   English   中英

Java多线程:意外的结果

[英]Java Multi-threading : Unexpected result

我正在开发一个企业应用程序。 我在多线程环境中运行应用程序时遇到了一些问题。 我正在编写一个程序,其中有一个变量,其值以非常快的速率更新(递增)(例如10000次更新/ persecond)。 循环运行一定的迭代,变量的值递增并存储在HashMap中。 一旦循环终止并且值打印HashMap中的变量。 我得到了变量的意外价值。

这是演示程序(请阅读评论以便更好地理解):

class test implements Runnable {

    static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    static AtomicInteger value_to_be_incremented_stored = new AtomicInteger(0); // variable whose value to be updated
    static AtomicInteger i = new AtomicInteger(0);  // this runs the loop

    @Override
    public void run() {

        for (i.set(0); i.get() < 100000; i.incrementAndGet()) {
            /*
                This loop should run 100000 times and when loop terminates according to me value of variable 
                "value_to_be_incremented_stored" should be 100000 as its value is incremented 
                100000 times the loop also runs 100000 times. 
            */
            System.out.println("Thread > " + Thread.currentThread() + "  " + value_to_be_incremented_stored.incrementAndGet());
            map.put("TC", value_to_be_incremented_stored.intValue());
        }

        System.out.println("Output by Thread  " + Thread.currentThread() + "     " + map.toString());
    }

    public static void main(String[] args) {

        test t1 = new test();
        Thread thread1 = new Thread(t1);
        thread1.setName("Thread 1");

        Thread thread2 = new Thread(t1);
        thread2.setName("Thread 2");

        Thread thread3 = new Thread(t1);
        thread3.setName("Thread 3");

        Thread thread4 = new Thread(t1);
        thread4.setName("Thread 4");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }
}

输出 (变化):

我的输出

问题 :我运行循环100000次(i.get() < 100000)然后变量value_to_be_incremented_stored值变得超过100000。

我发现了三个缺陷。 在比较循环计数器的点与增加循环计数器的位置之间的for循环中存在竞争条件。 您应该在一个步骤中执行此操作以获取原子操作:

for ( ; i.incrementAndGet() < 100000;  ) {

另一个是你的计数器增量和放在地图之间也存在竞争条件。 即使你将它们串联递增,任何线程都可以在内部具有不同的值(它位于循环中的不同点),并且它可以将先前的值放在全局映射中。 这里需要原子性以确保增加的值是您在循环中放置的值。

synchronized( lock ) { 
    value_to_be_incremented_stored.incrementAndGet();
    map.put("TC", value_to_be_incremented_stored.intValue());
}

最后由于某种原因, <比较为我产生了99999的值。 我不得不用<=来修复它。

(正如我们在评论中所讨论的那样,在每个for循环开始时设置i.set(0)并不是很明显的原因。我猜想有四个缺陷。)

class ThreadTestX implements Runnable {

    static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    static AtomicInteger value_to_be_incremented_stored = new AtomicInteger(0); // variable whose value to be updated
    static AtomicInteger i = new AtomicInteger(0);  // this runs the loop
    static final Object lock = new Object();

    @Override
    public void run() {

        for ( ; i.incrementAndGet() <= 100000;  ) {
            /*
                This loop should run 100000 times and when loop terminates according to me value of variable 
                "value_to_be_incremented_stored" should be 100000 as its value is incremented 
                100000 times the loop also runs 100000 times. 
            */
            synchronized( lock ) {
                value_to_be_incremented_stored.incrementAndGet();
    //            System.out.println("Thread > " + Thread.currentThread() + 
    //                 "  " + value_to_be_incremented_stored.get());
                map.put("TC", value_to_be_incremented_stored.intValue());
            }
        }

        System.out.println("Output by Thread  " + Thread.currentThread() 
                + "     " + map.toString());
    }

    public static void main(String[] args) {

        ThreadTestX t1 = new ThreadTestX();
        Thread thread1 = new Thread(t1);
        thread1.setName("Thread 1");

        Thread thread2 = new Thread(t1);
        thread2.setName("Thread 2");

        Thread thread3 = new Thread(t1);
        thread3.setName("Thread 3");

        Thread thread4 = new Thread(t1);
        thread4.setName("Thread 4");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }
}

输出:

run:
Output by Thread  Thread[Thread 4,5,main]     {TC=100000}
Output by Thread  Thread[Thread 3,5,main]     {TC=100000}
Output by Thread  Thread[Thread 1,5,main]     {TC=100000}
Output by Thread  Thread[Thread 2,5,main]     {TC=100000}
BUILD SUCCESSFUL (total time: 0 seconds)

事后判断:尽管标记正确,但我不确定我是否正确。 这里的问题是你试图让三件事情保持同步:循环计数器i ,要增加的值和地图。 允许在同步块之外执行这些中的任何一个可以邀请它们处于意外状态。 我认为以下可能更安全:

@Override
public void run() {

    for ( ;;  ) {
        synchronized( lock ) {
            if( i.incrementAndGet() <= 100000 ) {
                value_to_be_incremented_stored.incrementAndGet();
                map.put("TC", value_to_be_incremented_stored.intValue());
            }
            else
                break;
        }
    }
    System.out.println("Output by Thread  " + Thread.currentThread() 
            + "     " + map.toString());
}

这消除了将变量声明为AtomicInteger的需要,但是我没有看到如何确保它们的值在该循环执行时不会改变(由于某些其他线程)。

您的“演示程序”会同时出现两个缺陷。

缺陷#1是i.set( 0 )指出的i.set( 0 ) 你说你修正了,但你仍然得到超过100000的值。我也尝试过它,实际上,最终值仍然大于100000。

我修改了程序以便能够创建任意数量的线程,我尝试使用3,10和20个线程。 我的最终数字分别为100003,100009和100019。 看模式?

会发生的是,在最后一次迭代中,当i值为99999时,表达式i.get() < 100000对于所有线程都为真,因此所有线程都会再次执行。 i.incrementAndGet()子句直观地坐在i.get() < 1000000;旁边i.get() < 1000000; 但它直到循环结束才会执行。

因此,所有线程都有机会在最后一次迭代后再次增加i

每当新线程进入run方法时,它将通过调用i.set(0)通过for循环中的第一个语句将i count重置为零。

更新:修复重置问题后的下一步是逐步执行代码并查看线程的行为方式。 让我们说三个线程进入for循环,而第四个线程增加i 会发生什么是value_to_be_incremented_stored将增加3次而i只增加一次。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM