【问题标题】:Thread Confinement线程限制
【发布时间】:2011-09-09 20:29:09
【问题描述】:

我正在阅读 Java Concurrency in Practice 并且有点与线程限制概念混淆。书上是这么说的

当一个对象被限制在一个线程中时,这种用法自动是线程安全的,即使被限制的对象本身不是

那么当一个对象被限制在一个线程中时,没有其他线程可以访问它吗?这就是被限制在一个线程中的意思吗?如何将对象限制在线程中?

编辑: 但是如果我仍然想与另一个线程共享对象呢?假设线程 A 处理完对象 O 后,线程 B 想访问 O。那么,在 A 处理完 O 之后,O 是否仍被限制在 B 中?

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

【问题讨论】:

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


    【解决方案1】:

    见:http://codeidol.com/java/java-concurrency/Sharing-Objects/Thread-Confinement/

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

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

    【讨论】:

    • 请注意,在ThreadLocal 中存储对对象的引用确实不会确保对象将被限制在该线程中。您仍然可以通过不同的方式轻松地将对象泄漏给其他线程。
    • 除此之外,ThreadLocal 在你有一堆线程在做同样的事情时最有用。如果你有一个线程专门为它提供独特的东西,ThreadLocal 几乎没用。
    • 很遗憾我不能投票赞成 cHao 评论:) 在多线程开发的几十年中,我从未使用过,也从未尝试过使用任何 ThreadLocal 存储。线程堆栈是线程本地存储。在 OO 语言中,线程类的私有数据成员对我来说似乎也很“线程本地”:)
    • @Martin James - 当您需要通过堆栈传递对象但不想混淆方法时,ThreadLocale 很有用。例如,您的 DAO 可能会将 db 连接放入线程本地,这样它就不必将 db 连接传递给负责加载响应的所有方法。
    【解决方案2】:

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

    不,反之亦然:如果您确保没有其他线程可以访问某个对象,则称该对象仅限于单个线程。

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

    例如:如果对一个对象的only引用来自一个局部变量,那么该对象肯定被限制在一个线程中,因为其他线程永远无法访问局部变量。

    同样,如果对一个对象的only引用来自另一个已被证明被限制在单个线程中的对象,那么第一个对象被限制在同一个线程中。

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

    证明这样一个对象只能被单个线程访问,比证明一个对象在其整个生命周期中都被限制在一个单个线程中要困难得多,但是。

    在我看来,这些对象从来没有真正“被限制在一个线程中”(这意味着一个强有力的保证),而是可以说“一次只被一个线程使用”。

    【讨论】:

    • "没有将对象限制在单个线程中的语言或 JVM 级别的机制" => ThreadLocal 怎么样,只要您不明确地转义其他地方的引用?
    • @ThreadLocal 不确保引用仅限于单个线程。它只是避免通过此引用泄漏它。您仍然可以通过其他方式轻松泄漏它。
    • 您可以创建一个“代理”对象(可能在 InvocationHandler 的帮助下)并在每次方法调用时验证 Thread.currentThread() == YOUR_THREAD。但是你必须递归地沿着你的对象树执行此操作才能 100% 确定......
    【解决方案3】:

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

    这就是线程限制的意思——对象只能被一个线程访问。

    这就是被限制在一个线程中的意思吗?

    见上文。

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

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

    • 您创建一个新对象,然后
    • 您永远不会将对象的引用分配给实例或类变量,并且
    • 您永远不会调用为引用执行此操作的方法,
    • 那么对象将被线程限制。

    【讨论】:

    • 虽然您的答案肯定是正确的,但 OP 并不是唯一一个被书中文本所迷惑的人。如果如您所说,对象是“受限的”,例如。它的唯一引用在线程的堆栈上,并且它的引用永远不会从线程发出信号,那么很明显该线程是唯一可以访问该对象的线程。书中的文字暗示有一些特殊的锁定机制可用,例如“myThread.confine(myObject)”,而不是“正常”的语言约束。
    • @Martin James - 我面前有书本。如果您在上下文中阅读引用的句子,则根本不清楚。当然没有任何特殊机制可以实现限制。
    【解决方案4】:

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

    简单示例:

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

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        一种方法是“堆栈限制”,其中对象是限制在线程堆栈中的局部变量,因此没有其他线程可以访问它。在下面的方法中,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

        【讨论】:

          【解决方案7】:

          I 表示只有在一个线程中运行的代码才能访问该对象。

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

          【讨论】:

            【解决方案8】:

            最明显的例子是使用线程本地存储。请看下面的例子:

            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 并发的工作原理。您仍然必须将有问题的共享对象“安全地发布”到线程。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-03-28
              • 2020-07-04
              • 2011-09-14
              • 2019-03-15
              相关资源
              最近更新 更多