更好地了解螺纹结构的竞争条件

时间:2012-03-14 17:58:49

标签: java multithreading

我在并发和线程上经历了Oracle tutorial并遇到了以下几行:

  

警告:构建将在线程之间共享的对象时,要非常小心,对对象的引用不会过早“泄漏”。例如,假设您要维护一个包含每个类实例的List调用实例。您可能想要将以下行添加到构造函数中:instances.add(this);但是其他线程可以在构造对象完成之前使用实例来访问对象。

任何人都可以解释这些线的确切含义吗?

5 个答案:

答案 0 :(得分:4)

这是关于多线程环境中的施工竞争条件。另一个线程可以在构造函数完成之前或者在同步各个字段值之前开始使用类的实例,这意味着其他线程甚至之后将看到默认值它们似乎已在构造函数中初始化。这与double-check locking和其他共享问题存在同样的问题。

这是一个很好的阅读:


考虑以下构造函数(部分复制自IBM文章):

public class EventListener {
    private int field;
    public EventListener(EventSource eventSource) {
        // do our initialization
        field = 10;
        // register ourselves with the event source
        eventSource.registerListener(this);
    }
    public onEvent(Event e) { 
        // what is printed here?
        System.out.println("Field = " + field);
    }
}

由于Java优化和指令重新排序,无法保证field = 10行在registerListener(this) 之前完成。由于很可能事件正由另一个线程处理,因此您无法确定onEvent println是否会将field输出为0(默认值)或10。

即使我们确定赋值发生在registerListener调用之前,因为事件是在另一个线程中处理的,并且没有EventListener的同步,那么值可能仍然是{{ 1}}在该线程中,即使在调用构造函数的线程中它是0

仅当10被定义为field时才会保证订单。否则,您需要以某种方式同步final或使用EventListener关键字或volatile

答案 1 :(得分:4)

想象你有两个主题。左线程和右线程。左线程负责创建工作对象,右线程负责将它们用于工作。

当左线程完成创建对象时,它将对象放在右线程可以找到它的位置。它是一个变量,我们称之为w(对于工人而言),为简单起见,我们可以说它在某种程度上是全球可访问的。

正确的线程是一个循环。它会检查w是否为空。如果w实际上具有非null值,则在其上调用方法do

班级工作人员看起来像这样:

public class Worker {
    private int strength;
    private float speed;
    private String name;
    private String specialty;

    public Worker(int str, float spd, String n, String spc) {
        strength = str;
        speed = spd;
        name = n;
        specialty = spc;
    }

    public void do() {
        System.out.println("Worker " + name + " performs " + strength + " " + specialty + " at " + speed + " times per minute.");
    }
}

所以它是这样的(我已经尝试通过在每个列中设置它们各自的命令来说明两个线程。我希望它是可以理解的。记住,一次只有一个线程是活动的,所以这就是为什么总是只有指令一次一列)

left thread                                         | right thread
----------------------------------------------------|-----------------------------------------------------
                                                    |
Worker newWorker = new Worker(                      |               ...right thread sleeps...
                    4,                              |                           .
                    5.2f,                           |                           .
                    "mmayilarun",                   |                           .
                    "multithreaded programming"     |                           .
                );                                  |                           .
-- control jumps to constructor of Worker --        |                           .
strength = str                                      |                           .
speed = spd;                                        |       !!right thread wakes up and takes the focus!!
                                                    |   
                                                    |   if(w == null) {
                                                    |       Thread.sleep(500);
                                                    |   } else {
                                                    |       //this doesn't happen, since w is still null
                                                    |   }
                                                    |   
                                                    |           ...right thread goes back to sleep...
name = n;                                           |                           .
specialty = spc;                                    |                           .
-- control exits constructor of Worker --           |                           .
w = newWorker;                                      |       !!right thread wakes up and takes the focus!!
                                                    |       
                                                    |   if(w == null) {
                                                    |       //this doesn't happen, since w is NOT null anymore
                                                    |   } else {
                                                    |       w.do();
                                                    |   }
                                                    |

在这种情况下,一切都很顺利。在worker的构造函数完成之后左边的线程设置了w。但这样做是不是很愚蠢?想象一下,如果我们将调用w = instanceOfWorker放在worker构造函数中,我们可以节省多少钱。那么我们就不用担心记住实际设置w。

新的构造函数看起来像这样:

public Worker(int str, float spd, String n, String spc) {
    w = this;
    strength = str;
    speed = spd;
    name = n;
    specialty = spc;
}

现在,代码的流程可能看起来像这样:

left thread                                         | right thread
----------------------------------------------------|-----------------------------------------------------
                                                    |
Worker newWorker = new Worker(                      |               ...right thread sleeps...
                    4,                              |                           .
                    5.2f,                           |                           .
                    "mmayilarun",                   |                           .
                    "multithreaded programming"     |                           .
                );                                  |                           .
-- control jumps to constructor of Worker --        |                           .
w = this;   // danger!!                             |                           .
strength = str;                                     |                           .
speed = spd;                                        |       !!right thread wakes up and takes the focus!!
                                                    |   
                                                    |   if(w == null) {
                                                    |       //this doesn't happen, since w is NOT null at this point
                                                    |   } else {
                                                    |       w.do(); //here, w is not yet a fully initialized object,
                                                    |       //and the output is not the expected (if it works at all)
                                                    |   }
                                                    |   
                                                    |           ...right thread goes back to sleep...
name = n;                                           |                           .
specialty = spc;                                    |                           .
-- control exits constructor of Worker --           |

oracle有一个更复杂的例子,其中包含一组名为“instances”的对象。这是唯一的区别。

答案 2 :(得分:1)

这是并发问题,如果有2个或更多线程处于活动状态并调用相同的代码并使用相同的变量,那么您的构造函数或共享代码可能会被2个线程同时访问。问题是1个线程需要的东西,因为另一个线程得到不同的结果。

答案 3 :(得分:0)

这意味着在构造函数完成之前,不应该“泄漏”对象的引用。例如,请参阅以下代码:

// Constructor
public SomeObject() {
  // A holder class that's external to this class.
  ObjectInstances.LIST.add(this);

  // Further code constructing the object, setting fields, etc.
}

在此示例中,通过将其添加到外部List,可以将对新创建的SomeObject实例的引用提供给其他代码。这使得其他线程可以在构造函数完成之前访问对象,因此可以访问“不完整”对象。

答案 4 :(得分:0)

这意味着如果你有一个构造函数:

class Bob {
  List<Bob> instances = ...

  public Bob(){
    instances.add(this); //is bad 
  }
  public void do Operation(){ ... }
}

这是有问题的,因为如果你有多个线程(假设你有1个Bob对象/线程),如果在Bob实例1中你在ctor做instances.add(this)那么在Bob实例2中,你会看到Bob in instances变量。如果在Bob 2中你尝试instance.get(i).doOperation();它会失败,因为Bob的实例1没有完全实例化但它已经在instances变量中。