【问题标题】:Java threading and outputstream printinng issueJava线程和输出流打印问题
【发布时间】:2013-10-29 12:55:19
【问题描述】:

我正在尝试创建 5 个不同的线程,并尝试在每次将静态变量的计数加一时在其 run 方法中打印一个静态对象

这是程序的示例输出

pool-1-thread-1 Static Value before update 19
Thread going to sleep pool-1-thread-1
pool-1-thread-4 Static Value before update 19
Thread going to sleep pool-1-thread-4
pool-1-thread-3 Static Value before update 19
Thread going to sleep pool-1-thread-3
pool-1-thread-2 Static Value before update 19
Thread going to sleep pool-1-thread-2
pool-1-thread-5 Static Value before update 19
Thread going to sleep pool-1-thread-5
Thread coming out of sleep pool-1-thread-3  StaticTest.sInt 19
Thread coming out of sleep pool-1-thread-4  StaticTest.sInt 19
Thread coming out of sleep pool-1-thread-1  StaticTest.sInt 19
Thread coming out of sleep pool-1-thread-5  StaticTest.sInt 19
Thread coming out of sleep pool-1-thread-2  StaticTest.sInt 19

**pool-1-thread-5  OLD value 22 Static Value after update 23**
pool-1-thread-1  OLD value 21 Static Value after update 22
pool-1-thread-4  OLD value 20 Static Value after update 21
pool-1-thread-3  OLD value 19 Static Value after update 20
pool-1-thread-2  OLD value 23 Static Value after update 24

现在我的问题是,因为线程 3 首先从睡眠中出来,所以它必须首先打印,但是它的线程 5 首先打印,并且它的值也为 22,即静态变量在线程 5 之前增加了三倍拿着它,但是为什么我在向我打印递增值时看到一个随机顺序,它应该以与它们从睡眠中出来的顺序相同的顺序打印,即线程 3/4/1/5/2

请倾诉思绪?我错过了为什么一旦线程在睡眠后恢复运行状态时的随机行为

package com.test.concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class StaticTest {

    public static Integer sInt = new Integer(19);

    public static void main(String[] args) {

        ExecutorService es = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            es.execute(new StaticTask());
        }

    }

}

class StaticTask implements Runnable {

    public void run() {

        String name = Thread.currentThread().getName();
        System.out.println(name + " Static Value before update "
                + StaticTest.sInt);
        try {
            System.out.println("Thread going to sleep " + name);
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Thread coming out of sleep " + name + "  StaticTest.sInt " + StaticTest.sInt);

        int local = StaticTest.sInt;
        StaticTest.sInt = new Integer(local + 1);

        System.out.println(name + "  OLD value " + local +" Static Value after update "
                + StaticTest.sInt);

    }
}

【问题讨论】:

    标签: java multithreading concurrency java.util.concurrent


    【解决方案1】:

    您无法控制或确定性地知道哪个线程将首先执行,仅仅因为您首先启动线程并不意味着它将首先运行......

    您正在一个循环中执行 5 个线程,但不能保证第一个会先运行,第二个会运行第二个,依此类推...

    如果您确实希望线程以特定顺序运行,则必须执行一些联接或等待/通知逻辑。

    【讨论】:

      【解决方案2】:

      Java Language Specification 7 中的章节 17.4.3-5 处理了哪些类型的操作受 happen-before 规则的约束,以及通常可以预期多线程应用程序的执行顺序。 p>

      一旦您阅读了这些章节,您就会意识到执行顺序几乎没有保证。在多线程应用程序的情况下,我们可能认为自然和理所当然的事情通常是无效的。

      此外,还有内存模型 - 您无需同步即可访问变量 sInt。这样,您无法保证不同的线程会注意到对象引用已更改。您必须在修改变量的对象/线程之间使用公共锁,以确保其更改甚至可见。

      您可以使用synchronized 块和静态对象锁来做到这一点:

      // in class:
      static Object lock = new Object();
      
      
      // in run():
      synchronized(lock) {
          int local =StaticTest.sInt;
          StaticTest.sInt = new Integer(local + 1);
          System.out.println(name + "  OLD value " + local +" Static Value after update "
              + StaticTest.sInt);
      }
      

      这样synchronized 中的打印将被正确排序。

      【讨论】:

        【解决方案3】:

        您应该阅读 Java 中的并发性。你永远不应该期望线程以任何特定的顺序运行——尤其是在没有围绕共享资源同步代码的情况下。可能想start here

        【讨论】:

          【解决方案4】:

          调度的线程依赖于操作系统,这些线程有时间片

          【讨论】:

            【解决方案5】:

            从输出中,很明显thread-3 是第一个醒来的(是的,你是对的) 然后增加静态 int

            pool-1-thread-5  OLD value 22 Static Value after update 23
            pool-1-thread-1  OLD value 21 Static Value after update 22
            pool-1-thread-4  OLD value 20 Static Value after update 21
            pool-1-thread-3  OLD value 19 Static Value after update 20  --> static counter incr. from 19 to 20
            pool-1-thread-2  OLD value 23 Static Value after update 24
            

            在上面的打印语句中,从它们打印的值可以清楚地看出,首先唤醒的线程正在执行增量并打印值。

            不可预知的是打印顺序。

            如果你认为在低层次,System.out.print 语句是 Asynchronous,因此输出

            【讨论】:

            • 这里的异步是什么意思?所以如果线程 1 首先调用 sysout 它应该是第一个在控制台上打印的不是真的吗?
            • 如果线程 1 调用 sysout,那不一定是应该首先打印的那个。这就是我所说的异步的意思。。
            【解决方案6】:

            现在我的问题是,因为线程 3 首先从睡眠中出来,所以它必须首先打印,但是它的线程 5 首先打印,并且它的值也是 22

            由于有 5 个线程同时运行,因此给定语句集的执行顺序是不可预测的。即使线程以3/4/1/5/2 的顺序出现,也不能保证在执行其余语句时会保留相同的顺序。这就是为什么它被称为Asynchronous 执行。

            threads 3, 4, 1 可能已经执行了这些语句

            int local =StaticTest.sInt;
            StaticTest.sInt = new Integer(local + 1);
            

            一个接一个(以任何顺序),然​​后thread 5 有机会一次性执行语句:

            int local =StaticTest.sInt;
            StaticTest.sInt = new Integer(local + 1);
            System.out.println(name + "  OLD value " + local +" Static Value after update "
                            + StaticTest.sInt);
            

            你已经在控制台上打印了这个:

            pool-1-thread-5  OLD value 22 Static Value after update 23
            

            因为静态变量StaticTest.sInt 的值可能已经被threads 3, 4, 1 更新为22

            并且您应该始终对共享变量使用同步访问,以便一个线程所做的更改对其他人可见,并且在同步访问中,对共享变量的操作是原子的:

            public static synchronized Integer incrementSInt() {
                return StaticTest.sInt++;
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-12-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多