关于多线程操作单个变量进行加1问题的分析

  摘要:在多线程的学习中,我们经常会接触到一个例子,这个例子也是一个重要的面试题,在此我详细的分析这个问题

  问题:我们现有两个线程,这两个线程公共的对一个变量进行100次++操作,那么我们会得到怎样的结果?

  答案:我们的到的结果不固定,我们会得到一个在2~200之间的随机数。

  疑点:我们为什么会得到这样一个随机数呢?首先这个问题体现出的是多线程对公共资源进行读写操作的时候不加锁的后果,这个我们大多数人都可以理解,不加锁会导致读后写的问题,也就是我们在计算中会相互覆盖,导致我们的计算结果不可信,但是关键在于,我们为什么会得到一个2~200之间的数字,而不是1~200之间的数字呢

  分析:为了更加简洁明快的了解这个问题,我们先附上代码:

package com;

public class Try {
    public static void main(String args[]){
        final int[] a = {0};
        Thread t1 = new Thread(){
            @Override
            public void run(){
                for(int i = 0;i<10000;i++){
                    a[0]++;
                }
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run(){
                for(int i = 0;i<10000;i++){
                    a[0]++;
                }
            }
        };
        t1.start();
        t2.start();
    }
}

  在此存在两个线程对一个公共资源区进行++操作,各自进行100次,对于每个线程中的操作,我们可以分为三个部分:

1.获取到资源上的值
2.为数值进行加一操作
3.将自己计算得到的数值放回到内存上

  需要注意的是,在多线程中,执行完每一个操作之后,都有可能被切换除去执行另外的一个线程。因此我们不难假设一种最坏情况:A线程在获取到资源之后,进行了一次加1操作,之后便被线程B夺走了使用权,线程B加这些数值加到了99之后,正要进行第100次操作,但是在执行完第99次操作之后,就被线程A夺取了CPU使用权,线程A此时将1写在了内存上,将99覆盖了,之后线程B继续执行,B将获取到线程A刚刚写入的1,并进行了一次加1操作,得到了2,之后被A打断,A执行完自己,并将100写入到了内存中,之后B线程将2写入到内存中,覆盖了100,因此这种情况就是2,而最好情况就是这两个线程阴差阳错的没有发生覆盖的情况,正好加到了200。所以这个问题的结果就是2~200。

  需要注意的是在这个问题中结果是不会出现2以下的情况的,如果想出现结果为1,那么我们就需要保证1覆盖了一个100,这是因为我们需要保证让另外一个线程完全结束,否则这个结果一定还会被++,结果就不是1了,但是我们实际上不可能让一个线程得到为1的结果,因为其中一个线程是一定要执行100次的。我们如果想让结果最小,一定要让两个线程发生相互覆盖,因为需要相互覆盖,我们必须保证其中一个线程执行到最后一次,然后被覆盖,之后让另一个线程执行完,执行完后这个线程继续执行,在为刚才得到的1进行加一操作之后结束,简而言之,两个线程都必须执行,而相互覆盖的结果就是2。

  总而言之,得到2的极端过程就是:线程A执行,加一操作之后得到1,然后被线程B打断,B执行了99次之后被A打断,A执行到将1写入到内存之后又被B打断,B获取到1之后进行加1操作之后被A打断,A执行完毕得到了100,将100写入到了内存中,然后B线程中将自己的2写入到了内存中,2将100覆盖了,得到了2。因此在这个问题中,答案是2~200,记住这个答案。

总结分析:我再次探讨以下为什么在两个线程的自增操作中,对单个资源进行++操作的时候会出现2~200的情况,这是为什么呢?这是因为如果想要出现一个最小的极端情况,那么就需要两个线程对资源进行了相互覆盖,在其中一个线程中对这个资源加到了最大的时候,这个资源趋于结束的时候,用自身保存的一个最小的值进行覆盖,但是这个覆盖应该是相互的,我们探讨第一种极端情况,就是线程A将资源加到了100之后,被B线程当前持有的0覆盖了,但是0只是一个当下的值,线程B可不会停止执行,之后线程B会一直执行到100,导致最终的结果是100。而我门不得不退而求其次,让A线程执行到99的时候停止,这时让线程B中持有的1来覆盖,而线程B覆盖一次之后立即暂停,让线程A来获取到这个1而暂且不往下执行,之后线程A将执行到结束,将资源++到100,而线程A结束之后再让线程B执行,最终线程B将自己++得到的2来覆盖到资源上,我们得到了最小的一种情况就是2.
在这个问题中我们需要注意的是两点:每一个线程都必须完整的执行自己的所有操作,缺一不可;每一个线程中的每一个操作执行完毕之后,都有可能被挂起。
在该问题中,要想得到一个最小值,那么我们必须让整体中出现一个让资源被两个线程的最小值相互覆盖的情况,每个线程中的覆盖次数越少,覆盖位置月极端,我们得到的数值就越小。这里的意思是说,每个线程中都要有覆盖对方的行为,并且我们要保证每个线程中的覆盖对方的行为要最少,这样我们就可以保证资源最终值最少,这个极端情况就是在其中一个线程的第一次覆盖,在另一个线程的最后一次覆盖,也就是在1/99和99/1处覆盖,而不被覆盖的有效执行次数是2,因此我们得到的最小结果是2。