如何在线程之间共享对象以证明它不是线程安全的?

时间:2015-09-15 10:18:37

标签: java multithreading

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

class MutableInteger{
    private int value;

    public int get(){
       return value;
    }

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

这是我的线程实现: - 有一个主线程(主方法),并从中调用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类的每个访问。现在按照理论,每个对象都有自己的实例变量副本。在我的实现中,两个线程仅通过value实例访问MutableInteger变量。在我的情况下,value不会在线程之间共享,从而使我的MutableInteger线程安全。

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

我做错了吗?

3 个答案:

答案 0 :(得分:1)

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

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

一个可运行的对象(它更适合使用扩展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方法更改为可以让您更好地查看缺乏安全性的方法。你有非原子读取和添加,以及使用#34; stale&#34; (非易失性)变量。

要共享对象,请使用:

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.但当然它不会。并且它可能会在每次运行时打印不同的数字,有时甚至会为不同的线程打印不同的数字。

答案 1 :(得分:1)

您当前的代码是线程安全的,但通常您的类不是线程安全的。在t1.set()之前使用t1.start()是安全的,因为线程开始介绍发生在边缘之前(从启动线程内部可以看到在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)。

答案 2 :(得分:0)

反对意见

我会说MutableInteger线程安全的。

当我声称一个类是线程安全的时,我的意思是没有办法从不同的线程重复调用它的方法会导致类或类的实例违反其API契约。

MutableInteger的API合约是什么?

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

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

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

  

...在将对象放入BlockingQueue 之前的一个线程中的操作在<{1}}之后访问或删除该元素之后的之前发生的操作另一个线程。

我没有看到对BlockingQueue类做出任何此类声明,因此,如果它表现得像普通的int而不是像{{1}那样行事,我就不会称它为“已损坏” } int。