为什么主线程不能比较其他线程更新的两个变量?

时间:2016-11-19 06:15:47

标签: java multithreading

我想编写两个递增数字并减少数字的线程,以及一个确定两个数字何时相等的主线程。例如,一个数字从0开始,另一个数字从10开始...当它们都是5时,主线程应该识别它们是相等的并打印"它们相遇!"。

在此代码中,主线程无法成功比较numupnumdown

public class Number implements Runnable {
    public static int numup = 0;
    public static int numdown = 10;

    public Number() {
    }

    public static void main(String args[]) {
        Number number = new Number();
        Thread T1 = new Thread(number, "up");
        Thread T2 = new Thread(number, "down");
        T1.start();
        T2.start();

        while (true) {
            if (numup == 5 && numdown == 5) {
                System.out.println("Meet!");
                System.exit(0);
            }
        }

    }

    public void run() {
        while (true) {
            if (Thread.currentThread().getName().equals("up")) {

                numup++;
                System.out.println(numup);

            } else if (Thread.currentThread().getName().equals("down")) {

                numdown--;
                System.out.println(numdown);
            }

            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                System.out.println("wake!");
            }
        }

    }
}

失败的结果:

  

1   
  9   
  8   
  2   
  7   
  3   
  6   
  4   
  五   
  五   
  6   
  4   
  7   
  3   
  8   
  2   
  1   
  9

但是,当我让主线程休眠几毫秒时,它可以工作:

public class Number implements Runnable {
    public static int numup = 0;
    public static int numdown = 10;

    public Number() {
    }

    public static void main(String args[]) {
        Number number = new Number();
        Thread T1 = new Thread(number, "up");
        Thread T2 = new Thread(number, "down");
        T1.start();
        T2.start();

        while (true) {

            try {
                Thread.sleep(10);
            } catch (Exception e) {
                System.out.println(Thread.currentThread().getName() + "was waked!");
            }
            if (numup == 5 && numdown == 5) {
                System.out.println("They Meet!");
                System.exit(0);
            }
        }

    }

    public void run() {
        while (true) {
            if (Thread.currentThread().getName().equals("up")) {

                numup++;
                System.out.println(numup);

            } else if (Thread.currentThread().getName().equals("down")) {

                numdown--;
                System.out.println(numdown);
            }

            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                System.out.println("wake!");
            }
        }

    }
}

成功的结果:

  

1   
  9   
  2   
  8   
  3   
  7   
  4   
  6   
  五   
  五   
  他们见面了!

为什么增加的延迟使其有效?

3 个答案:

答案 0 :(得分:2)

这可能是因为CPU缓存。当数字线程更新变量的值(这从其CPU缓存到主存储器)时,相应主线程的CPU缓存可能没有更新。

因此,当主线程检查变量的值时,它仍然是旧值。

  1. 您可以使用易变性。 OR
  2. 使用 AtomicInteger 进行这些操作。
  3. 您可以参考此link

      

    在线程操作非易失性变量的多线程应用程序中,出于性能原因,每个线程可以在处理它们时将变量从主存储器复制到CPU缓存中。如果您的计算机包含多个CPU,则每个线程可以在不同的CPU上运行。这意味着,每个线程可以将变量复制到不同CPU的CPU缓存中。

         

    对于非易失性变量,无法保证Java虚拟机(JVM)何时将数据从主内存读取到CPU缓存中,或将数据从CPU缓存写入主内存。

    <强>易失性:

    public static volatile int numup = 0;
    public static volatile int numdown = 10;
    

    原子整数:

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Number implements Runnable {
        public static AtomicInteger numup = new AtomicInteger(0);
        public static AtomicInteger numdown = new AtomicInteger(10);
    
        public Number() {
        }
    
        public static void main(String args[]) {
            Number number = new Number();
            Thread T1 = new Thread(number, "up");
            Thread T2 = new Thread(number, "down");
            T1.start();
            T2.start();
    
            while (true) {
                if (numup.get() == 5 && numdown.get() == 5) {
                    System.out.println("Meet!");
                    System.exit(0);
                }
            }
        }
    
        public void run() {
            while (true) {
                if (Thread.currentThread().getName().equals("up")) {
    
                    numup.incrementAndGet();
                    System.out.println(numup);
    
                } else if (Thread.currentThread().getName().equals("down")) {
    
                    numdown.decrementAndGet();
                    System.out.println(numdown);
                }
    
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    System.out.println("wake!");
                }
            }
    
        }
    }
    

答案 1 :(得分:1)

快速回答 - 将volatile修饰符添加到numdown和numup。

答案很长: 你的问题是,由于以下原因,其他线程无法看到numdown和numup已经改变:

  1. JVM可以优化和重新排序字节码指令的执行顺序。
  2. 现代处理器也执行指令重新排序
  3. 该值缓存在处理器的缓存行(L1,L2,L3缓存级别)中。
  4. 因此,当你引入一个volatile变量时,java保证从一个线程写入将发生发生 - 之前与读取的关系形成另一个线程,从而使更改对另一个线程可见。在更低级别,它可能会引入内存屏障

    无论如何,它不适合SO答案来正确解释它是如何工作的,但如果你有兴趣深入研究这个话题,你可以阅读/观看一些优秀的资源。

    干杯!

答案 2 :(得分:0)

有趣的人和叶戈尔给出的好答案。只是添加我的观察结果,即使您在if (numup == 5 && numdown == 5)方法的while loop内编写run()支票,程序也会暂停。

如果您想试用volatile关键字 public static volatile int numup = 0; public static volatile int numdown = 10;

volatile关键字将确保您的线程不会缓存变量的值,并始终从主内存中检索它。