【问题标题】:Why does value not change inside thread?为什么线程内部的值不会改变?
【发布时间】:2021-04-09 05:14:50
【问题描述】:

当我为变量分配一个新值时,它在 start() 之后不会改变,但是在我使用 join() 之后它会改变。为什么会发生这种情况?在这种情况下,int a 是否应该是 volatile 的?

class SampleThread extends Thread {

    private static int a = 0;

    @Override
    public void run() {
        a = 3;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t2 = new Thread(new SampleThread());
        t2.start();
        System.out.println(a);
    }
}

【问题讨论】:

  • 为什么你希望新线程在主线程继续执行println之前立即获得控制权?无法保证线程被调度的顺序。
  • 确实如此,但主线程继续使用System.out.println(a);,然后第二个线程将更改a(因此更改将发生在打印之后)。为了增加第二个线程在主线程打印之前更新a 的机会,让主线程在调用println 之前稍等片刻。您可以通过在System.out.println(a); 之前调用Thread.sleep(1000); 之类的东西来做到这一点。
  • 如果应该执行 2 个线程,那么可能是在 JVM 退出之前只执行了 1 个线程(主线程);为了防止这种情况,我们可以使用 join() 以便 JVM 等到第二个线程完成后再终止。我说的对吗?
  • 如果一个非守护线程仍然处于活动状态,那么 JVM 将不会退出。由于您的 t2 线程未设置为守护进程,因此您的应用程序仅在 main 线程完成和 t2 完成时才会退出。 .join 用于强制调用该方法的线程等待其他线程完成其工作。在您的情况下,如果您在main 中调用t2.join(),则主线程将等到t2 完成其工作,然后才会继续(但在这种情况下,它甚至会破坏启动单独线程的目的)。跨度>
  • 如果您在 t2 上明确地 .join(),则不需要 volatile - 因为加入线程会建立先发生边缘。

标签: java multithreading volatile


【解决方案1】:

要查看发生了什么,试试这个:

    ...
    @Override
    public void run() {
        System.out.println("start of Thread");
        a = 3;
        System.out.println("end of Thread");
    }
    ...

run方法改变,其余代码不变

【讨论】:

    【解决方案2】:

    是的,它需要 volatile。

    每个线程都有一个邪恶的硬币。线程在读取或写入字段时掷硬币:正面,并且线程使用它自己的本地(到线程)副本;如果写入,该更新根本不会反映给所有其他线程,他们仍然会看到“旧”值,如果读取,同样的处理:读取它所拥有的任何内容,即使其他线程已经更新了它。即使他们在一小时前就这样做了。 Tails,它确实刷新了其他线程对这个东西的看法,并且不会使用本地副本。

    硬币是邪恶的:它不是一个公平的硬币。它会在今天的每一次工作,明天的每一次工作,以及在测试套件期间的每一次工作,并且在整个一周内,你都可以为早期采用的客户提供服务。然后就在那个大客户进来并且你正在做演示的时候?它每次都会翻转以破坏您的应用程序。那种邪恶。

    因此,您必须消除所有抛硬币现象,或至少确保抛硬币的结果不会影响您的应用程序

    做到这一点的方法是建立先到先得的关系。在由 VM 执行的任何 2 行 java 代码之间,有一组规则来确定这两行是否具有这样的关系:保证在另一行之后运行。这与他们是否这样做无关(他们运行的时间戳完全不相关),而是Java Memory Model 是否规定存在这种关系。

    如果是,则不掷硬币:“根据 JMM 之前的线”所做的任何事情,对于之后的线肯定是可见的。但是,如果 JMM 没有明确说明这种关系存在,硬币被翻转,你输了

    一个微不足道的'comes before'关系在一个线程中:x = 5; System.out.println(x);微不足道有这样的关系;他们在同一个线程中运行,一个接一个。那是免费赠品。

    但是在线程之间,天啊。您需要 synchronizedvolatile 或调用内部执行这些操作的代码或具有其他机制来确保它(提示:在 java.util.concurrent 包中有 很多 很棒的东西,顾名思义,它通常以非常有效的方式是线程安全的。例如,AtomicInteger 几乎总是比volatile int 好得多,并且可以做更多的事情,例如 CAS 操作,而 volatile int 无法做到。

    【讨论】:

    • 如果应该执行 2 个线程,那么可能是在 JVM 退出之前只执行了 1 个线程(主线程);为了防止这种情况,我们可以使用 join() 以便 JVM 等到第二个线程完成后再终止。我说的对吗?
    • 不,直到所有线程*都完成后,VM 才会退出。 *) 如果你在一个线程上设置了 'daemon' 标志,它不会被计算在内。没有必要加入这里。请注意,加入是建立先入为主的一种方法。线程 X 执行的所有语句都在X.join() 的分辨率之前。这样,在加入后,保证变量更新。您需要阅读 JMM 和 CBCA 规则的完整列表,或者放弃通过字段在线程之间进行通信。
    【解决方案3】:

    如果你希望不同线程中的事情以特定的顺序发生——在这种情况下,a = 2 在 `System.out.println(a)' 之前执行——那么你必须编写代码来实现它订单发生。

    在这个小例子中,没有做任何实际工作,几乎任何你能做的事情都会使线程的使用变得毫无意义。主线程可以“加入”将a 设置为 2 的线程,但您所获得的只是一种执行可在单个线程中执行的代码的昂贵方式。

    【讨论】:

    • 但这显然不是为了实际应用,而是为了训练目的。所以你说的真的无所谓。
    【解决方案4】:

    主线程应该等待另一个被执行 一种解决方案是使用 join()

    class SampleThread extends Thread {
    
        private static int a = 0;
    
        @Override
        public void run() {
            a = 3;
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread t2 = new Thread(new SampleThread());
            t2.start();
    
            // wait for completion of t2
            t2.join()
    
            System.out.println(a);
        }
    }
    

    【讨论】:

    • 但是如果在 t2 内部没有赋值,那么为什么 t2 中的代码会立即触发呢?当守护线程处于活动状态时,JVM 会退出吗?
    猜你喜欢
    • 1970-01-01
    • 2016-06-20
    • 1970-01-01
    • 1970-01-01
    • 2015-06-17
    • 2019-09-10
    • 2018-11-06
    • 2015-10-14
    • 1970-01-01
    相关资源
    最近更新 更多