【问题标题】:Java synchronization and data consistencyJava同步和数据一致性
【发布时间】:2017-01-08 14:32:21
【问题描述】:

考虑以下简单示例:

public class Example extends Thread {
    private int internalNum;

    public void getNum() {
        if (internalNum > 1)
            System.out.println(internalNum);
        else 
            System.out.println(1000);
    }

    public synchronized modifyNum() {
        internalNum += 1;
    }

    public void run() {
        // Some code
    }
}

假设代码执行分为两个线程。假设会发生以下事件序列:

  • 第一个线程访问getNum方法并缓存internalNum,此时为0。
  • 同时第二个线程访问modifyNum方法获取锁,将internalNum更改为1并退出释放锁。
  • 现在,第一个线程继续执行并打印internalNum

问题是控制台上会打印什么?

我的猜测是,这个假设的示例将导致在控制台上打印 1000,因为读写刷新仅在进入或离开同步块时强制在特定线程上。因此,第一个线程将愉快地使用它的缓存值,而不知道它已被更改。

我知道使internalNum volatile 可以解决可能的问题,但是我只是想知道天气是否真的有必要。

【问题讨论】:

  • Java 内存模型中没有缓存和刷新的概念。正如你所说,不能保证 getNum() 看到的东西与 0 不同。在你的情况下,我会删除同步并改用 AtomicInteger。
  • internalNum 不是静态的,不需要同步访问它,因为它本身就是一个 Thread 对象,每个对象都有自己的 internalNum 副本。
  • @AlexC 你几乎是正确的。你不知道我是如何运行线程的。我本可以写:Example myExample = new Example(); Thread t1 = new Thread(myExample); Thread t2 = new Thread(myExample); t1.start(); t2.start(); 当然,我不会那样做。
  • 是的,因为您将基类设为 Thread 而不是 Runnable,这使得您不清楚您打算将其作为 Runnable 调用,同时保留从 Thread 继承添加的所有额外权重。现在,由于您的代码中对 internalNum 的访问未同步,因此行为未定义。您需要同步 getter 或使用 AtomicInteger。
  • 我认为,如果您只看到 0 之外的任何内容,它将符合规范,但是我想不出任何真实世界的虚拟机/架构会以这种方式运行。有一些 testvm 标志确实表现得那么病态。参见例如stackoverflow.com/a/17830049/13189

标签: java memory synchronized volatile flush


【解决方案1】:

假设代码执行分为两个线程。 它不退出。然而,一个资源(方法、字段)可以被两个线程以并发方式访问。

我认为你混合了一些东西。您的课程扩展了Thread,但您的问题是关于通过并发线程访问同一实例的资源。

这是适合您问题的代码。

线程间共享资源:

public class SharedResource{
    private int internalNum;

    public void getNum() {
        if (internalNum > 1)
            System.out.println(internalNum);
        else 
            System.out.println(1000);
    }

    public synchronized modifyNum() {
        internalNum += 1;
    }

    public void run() {
        // Some code
    }   
}

线程和运行代码:

public class ThreadForExample extends Thread {

    private SharedResource resource;

    public ThreadForExample(SharedResource resource){
       this.resource=resource;
    }

    public static void main(String[] args){
       SharedResource resource = new SharedResource();
       ThreadForExample t1 = new ThreadForExample(resource);
       ThreadForExample t2 = new ThreadForExample(resource);
       t1.start();
       t2.start();
    }
}

你的问题:

假设发生以下事件序列:

第一个线程访问 getNum 方法并缓存 internalNum 目前为0。同时第二个线程访问 modifyNum 方法获取锁,将 internalNum 更改为 1 和 退出释放锁。现在,第一个线程继续执行并 打印 internalNum

在您的场景中,您给人的印象是modifyNum() 方法执行会阻止其他线程访问非同步方法,但事实并非如此。
getNum() 不同步。因此,线程不需要获取对象上的锁来执行它。在这种情况下,输出仅取决于哪个线程首先执行了指令:

internalNum += 1;

 System.out.println(internalNum);

【讨论】:

  • 这不是我的意思,我完全知道 modifyNum 不会阻塞其他执行块,getNum 也不会被阻塞。这只是可能发生的一系列假设事件。
  • 另外,我不太确定你的回答是否正确。参考我帖子中的cmets。控制台上打印的内容不仅仅取决于执行顺序。由于第一个线程的线程本地缓存,internalNum += 1; 可能会先执行,然后打印 1000。
  • 好的,我明白你的询问了。你的意思是 getNum() 被一个线程调用并且线程在之后停止?是的,在这种情况下,我会说结果并不是真正可预测的,因为它取决于多种因素:CPU、硬件优化、操作系统和 VM 版本。但无论如何,如果没有它,你已经有一个不可预测的行为:)
  • 是的,类似的。第一个线程不必停止,而是以某种方式停止,而第二个线程不间断地执行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-26
  • 1970-01-01
  • 2012-01-28
  • 2021-07-19
  • 1970-01-01
相关资源
最近更新 更多