【问题标题】:How to share an object between threads to demonstrate that it is not thread-safe?如何在线程之间共享一个对象以证明它不是线程安全的?
【发布时间】:2015-09-15 22:01:32
【问题描述】:

MutableInteger 被视为线程安全类,因为它在从不同线程调用时可能经常返回陈旧值。

class MutableInteger{
    private int value;

    public int get(){
       return value;
    }

    public void set(int value){
       this.value=value;
    }   
}

这是我的线程实现:- 有一个主线程(main 方法)并从中调用 MyThread。

class MyThread extends Thread{
    private int num;

    public void set(int num){
      this.num=num;
    }

    public void run(){
      MutableInteger mi=new MutableInteger();
      mi.set(num);
      System.out.println("Mythread "+mi.get());
    }
}

class MutableIntegerTest{
    public static void main(String[] args){
    MyThread t1=new MyThread();
    t1.set(10);     
    t1.start();
    MutableInteger mi=new MutableInteger();
    mi.set(99);
    System.out.println("Main thread" +mi.get());
    }
   }

现在这是我无法理解的。要访问状态变量value,我们需要使用MutableInteger 类的getter 和setter 方法,因此对于每次访问,我们都需要实例化MutableInteger 类。现在根据理论,每个对象都有自己的实例变量副本。在我的实现中,两个线程仅通过MutableInteger 实例访问value 变量。在我的情况下,value 不会在线程之间共享,从而使我的 MutableInteger 线程安全。

我应该怎么做才能让线程共享value?我是否应该公开该对象以便我可以在两个线程中使用相同的对象?但这不会将对象公开给所有可用的类和线程吗?

我做错了吗?

【问题讨论】:

  • 你有什么问题?除了MyThread.num 之外没有共享数据,并且只有在线程尚未启动时才会设置。或者更确切地说:你想实现什么?
  • 考虑 AtomicInteger。
  • 那么我应该如何从多个线程访问同一个实例。我应该公开实例还是应该将实例从 1 个线程传递给另一个线程??
  • 鉴于 MutableInteger 类,我只是想知道如何为其编写线程类。
  • ..._我只是想知道如何为它写一个线程类_。这意味着什么? “它的线程类”会做什么?

标签: java multithreading


【解决方案1】:

如果您想测试您的MutableInteger 以亲眼看到它不是线程安全的,您必须在线程之间共享一个对象。如果每个线程都创建了自己的MutableInteger 实例f,那么正如您所注意到的,您实际上并没有测试那个特定的东西,因为它只被创建它的单个线程访问。

所以你需要创建一个实例,并让两个线程都使用它。例如:

一个可运行的对象(最好使用扩展Thread的类):

class MutableIntegerTester implements Runnable {

    private int num;
    private MutableInteger mutableInteger;

    public MutableIntegerTester(int num, MutableInteger mutableInteger) {
        this.num = num;
        this.mutableInteger = mutableInteger;
    }

    @Override
    public void run() {

        for ( int i = 0; i < 1000; i++ ) {
            mutableInteger.set(mutableInteger.get() + num);
        }
        System.out.println(mutableInteger.get());
    }

}

请注意,我将run 方法更改为可以让您更好地了解缺乏安全性的方法。您既有非原子读取和添加,也有“陈旧”(非易失性)变量的使用。

要共享对象,请使用:

public static void main(String[] args) {
    MutableInteger sharedObject = new MutableInteger();
    MutableIntegerTester tester1 = new MutableIntegerTester(10, sharedObject);
    MutableIntegerTester tester2 = new MutableIntegerTester(99, sharedObject);
    new Thread(tester1).start();
    new Thread(tester2).start();
}

现在,如果 mutableInteger 是线程安全的,您会期望结果总是打印 109000。但当然不会。它可能会在每次运行时打印不同的数字,有时甚至会为不同的线程打印不同的数字。

【讨论】:

  • 这并没有回答“如何在线程之间共享变量”的问题,您只展示了如何获取与线程相关的错误,因此您的答案是无用的。
  • @KrzysztofCichocki 它确实如此——尤其是考虑到 OP 的 cmets 解释了他实际上在问什么。
  • 请注意,这个测试的编写方式并不完全正确。 mutableInteger.set(mutableInteger.get() + num); 不行,因为当一个线程获取 mutableInteger 的值,用 num 汇总并返回结果时,另一个线程可以多次这样做。因此,即使 ImmutableInteger 将是完全线程安全的,上面的测试也不会通过,因为它会带来一些竞争条件。
【解决方案2】:

您当前的代码是线程安全的,但通常您的类不是线程安全的。在t1.start() 之前使用t1.set() 是安全的,因为线程启动引入了happens-before 边缘(从启动的线程内部,您可以看到在Thread.start() 调用之前在启动线程中发生的所有事件)。但是,如果您在线程启动后调用 t1.set(),则不能保证线程会看到该新值。

MutableInteger 也一样。目前,您不会在线程之间共享 MutableInteger 实例,因此它们可以正常工作。但是,如果您共享,则不能保证更改会可见。例如,考虑您的 run() 方法:

public void run(){
  MutableInteger mi=new MutableInteger();
  mi.set(num);
  System.out.println("Mythread "+mi.get());
}

目前 JIT 编译器可以理解 MutableInteger 对象没有副作用并优化它对象,有效地将您的方法转换为:

public void run(){
  System.out.println("Mythread "+num);
}

即使没有 JIT 编译器优化,仍然存在内存可见性问题(MutableInteger 对象可以缓存在 CPU 核心 L1 缓存中,而其他 CPU 核心将看不到更新的值)以及 CPU 指令重新排序。因此,如果没有 volatile 关键字,就不能保证另一个线程会看到更改。添加volatile 关键字可以有效地将MutableInteger 转换为现有的AtomicInteger(其中包含单个int volatile 字段以及更方便的方法以及setter 和getter)。

【讨论】:

    【解决方案3】:

    反对意见

    我会说MutableInteger线程安全的。

    当我声称一个类是线程安全的时,我的意思是,从不同线程对其方法的重叠调用不会导致该类或该类的实例违反其 API 协定。

    MutableInteger的API合约是什么?

    OP 没有说,但任何理性的程序员都可以看到它的作用。

    我会争辩说,只要 get() 返回初始值 0 或之前设置的某个值 ();并且只要 get() 在同一线程 t 中返回一个较新的值之后,永远不会在线程 t 中返回一些旧值,那么我认为该类的行为方式是合理的。

    保证方法调用之间的任何类型发生之前关系的Java 库类通常在其文档中详细说明这些保证。例如,来自java.util.concurrent.BlockingQueue 接口的javadoc:

    ...在将对象放入BlockingQueue 之前的线程中的操作happen-before 在另一个线程中从BlockingQueue 访问或删除该元素之后的操作。

    我没有看到针对 MutableInteger 类的任何此类声明,因此,例如,如果它的行为类似于普通 int 而不是 volatile int,我不会将其称为“损坏”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多