线程限制

时间:2011-06-07 07:28:56

标签: java multithreading concurrency thread-safety thread-confinement

我正在阅读Java Concurrency in Practice,并且与线程限制概念相混淆。这本书说明了

  

当一个对象局限于一个线程时,即使受限对象本身不是

,这种用法也会自动进行线程安全的。

因此,当一个对象被限制在一个线程中时,没有其他线程可以访问它吗?是否意味着被限制在线程中?如何将对象限制在线程中?

修改 但是,如果我仍然想与另一个线程共享该对象呢?假设在线程A完成对象O之后,线程B想要访问O.在这种情况下,在A完成后,O仍然可以被限制在B吗?

使用局部变量是一个肯定的例子,但这只是意味着你不与其他线程(AT ALL)共享你的对象。在JDBC连接池的情况下,一旦线程完成该连接,它就不会将一个连接从一个线程传递到另一个线程(因为我从未使用过JDBC,所以完全无能为力)。

8 个答案:

答案 0 :(得分:40)

  

因此,当一个对象局限于一个线程时,没有其他线程可以访问它吗?

不,这是另一种方式:如果你确保没有其他线程可以访问某个对象,那么该对象被认为仅限于一个线程。

没有语言或JVM级别的机制将对象限制在单个线程中。您只需确保对该对象的引用不会转义到另一个线程可以访问的位置。有些工具有助于避免泄漏引用,例如ThreadLocal类,但确保没有任何引用泄露到任何地方。

例如:如果对象的引用来自本地变量,则该对象肯定仅限于单个线程,因为其他线程永远不能访问局部变量。

类似地,如果对象的唯一引用来自另一个已被证明局限于单个线程的对象,则该第一个对象被限制在同一个线程中。

广告编辑:实际上,您可以拥有一个对象,该对象在其生命周期内一次只能由一个线程访问,但该单个线程会更改(JDBC Connection对象从连接池是一个很好的例子。)

证明这样的对象只能由单个线程访问,这比在一生中仅限于单个线程的对象要难得多,但是。

在我看来,这些对象永远不会“局限于单个线程”(这意味着强有力的保证),但可以说“一次只能由一个线程使用”。

答案 1 :(得分:10)

最明显的例子是使用线程本地存储。请参阅以下示例:

class SomeClass {
    // This map needs to be thread-safe
    private static final Map<Thread,UnsafeStuff> map = new ConcurrentHashMap<>();

    void calledByMultipleThreads(){
        UnsafeStuff mystuff = map.get(Thread.currentThread());
        if (mystuff == null){
            map.put(Thread.currentThread(),new UnsafeStuff());
            return;
        }else{
            mystuff.modifySomeStuff();
        }
    }
}

UnsafeStuff对象本身“可以与其他线程共享”,如果你在运行时将一些其他线程而不是Thread.currentThread()传递给地图的get方法,你会得到属于其他线程的对象。但你选择不。这是“仅限于线程的用法”。换句话说,运行时条件使得对象实际上永远不会在不同的线程之间共享。

另一方面,在下面的示例中,对象自动局限于线程,因此,“对象本身”仅限于线程。从某种意义上说,无论运行时条件如何,都无法从其他线程获取引用:

class SomeClass {
    void calledByMultipleThreads(){
        UnsafeStuff mystuff = new UnsafeStuff();
        mystuff.modifySomeStuff();
        System.out.println(mystuff.toString());
    }
}

这里,UnsafeStuff在方法中分配,并在方法返回时超出范围。换句话说,Java规范静态地确保对象始终局限于一个线程。因此,确保限制不是运行时条件或使用它的方式,而是Java规范。

事实上,现代JVM有时会在堆栈上分配这样的对象,这与第一个示例不同(没有亲自检查过这个,但我认为至少目前的JVM没有这样做。)

然而换句话说,在第一个例子中,JVM无法确定对象是否仅限于一个线程内,只需查看calledByMultipleThreads()内部(谁知道其他方法正在弄乱SomeClass.map })。在后一个例子中,它可以。


  

编辑:但如果我还想要怎么做呢   与另一个线程共享对象?   让我们说在线程A完成之后   对象O,线程B想要   访问O.在这种情况下,O仍然可以   A完成后局限于B?

在这种情况下,我不认为它被称为“受限制”。执行此操作时,您只需确保不同时访问对象。这就是EJB并发的工作原理。您仍然必须将有问题的共享对象“安全地发布”给线程。

答案 2 :(得分:6)

  

因此,当一个对象被限制在一个线程中时,没有其他线程可以访问它吗?

这就是线程限制的含义 - 对象只能被一个线程访问。

  

它是否意味着被限制在线程中?

见上文。

  

如何将对象限制在线程中?

一般原则是不要将引用放在允许另一个线程看到它的地方。枚举一组确保这一点的规则有点复杂,但(例如)if

  • 您创建了一个新对象,
  • 您永远不会将对象的引用分配给实例或类变量,而
  • 你永远不会调用一个方法来做参考,
  • 然后该对象将被线程限制。

答案 3 :(得分:5)

我想这就是想说的。就像在run方法中创建一个对象而不是将引用传递给任何其他实例一样。

简单示例:

public String s;

public void run() {
  StringBuilder sb = new StringBuilder();
  sb.append("Hello ").append("world");
  s = sb.toString();
}

StringBuilder实例是线程安全的,因为它仅限于线程(执行此run方法)

答案 4 :(得分:3)

一种方法是“堆栈限制”,其中对象是局限于线程堆栈的局部变量,因此没有其他线程可以访问它。在下面的方法中,list是局部变量,不会从方法中转义。该列表不必是线程安全的,因为它仅限于执行线程的堆栈。没有其他线程可以修改它。

public String foo(Item i, Item j){
    List<Item> list = new ArrayList<Item>();
    list.add(i);
    list.add(j);
    return list.toString();
}

将对象限制为线程的另一种方法是使用ThreadLocal变量,该变量允许每个线程拥有自己的副本。在下面的示例中,每个线程都有自己的DateFormat对象,因此您不必担心DateFormat不是线程安全的事实,因为多线程不会访问它

private static final ThreadLocal<DateFormat> df
                 = new ThreadLocal<DateFormat>(){
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyyMMdd");
    }
  };

Further Reading

答案 5 :(得分:0)

请参阅:http://codeidol.com/java/java-concurrency/Sharing-Objects/Thread-Confinement/

  

更正式的维护方式   线程限制是ThreadLocal,   这允许你关联一个   具有值保持的每线程值   宾语。 Thread-Local提供get和   设置维护a的accessor方法   每个值的单独副本   使用它的线程,所以get返回   传递给set的最新值   从当前正在执行的线程。

它拥有每个线程的对象副本,线程A无法访问线程B的副本并且如果您将专门执行它则打破它的不变量(例如,将ThreadLocal值分配给静态变量或使用其他方法公开它)

答案 6 :(得分:0)

这正是它的含义。对象本身只能由一个线程访问,因此是线程安全的。 ThreadLocal个对象是一种绑定到唯一线程的对象

答案 7 :(得分:0)

我的意思是只有在一个线程中运行的代码才能访问该对象。

在这种情况下,对象不需要是“线程安全的”