【问题标题】:Can object reference from thread local variable be shared to other threads safelly?来自线程局部变量的对象引用可以安全地共享给其他线程吗?
【发布时间】:2016-01-18 13:31:38
【问题描述】:

考虑一个场景:

MySessionObject object = Session.getObject();

//then object is passed to Runnable task.

private class MyTask implements Runnable {

    private final MySessionObject object;

    public SaveVisitorTask(MySessionObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        MyDao dao = new MyDao();
        MySessionObject savedObject = dao.save(object);
        this.object.setId(savedObject.getId());
    }

}

说明:

  1. 从会话中检索的对象,例如线程局部变量。
  2. 然后将其传递给 Runnable 任务,在该任务中异步持久化
  3. 然后从保存的对象到线程局部变量中的对象设置新的 id。

问题很简单——代码线程安全吗?很明显,线程局部变量(会话)不能 共享给线程,但是它所拥有的引用呢? this.object.setId(savedObject.getId()); 语句是否影响原始对象?

P.S. 如果这段代码不是线程安全的(我个人相信),有人能解释一下原因吗?

【问题讨论】:

  • 任何对象可以在线程之间共享。您所要做的就是将对该对象的引用放在多个线程可以看到它的地方。但是,在ThreadLocal 对象中存储对共享对象的引用,这不会有点违背ThreadLocal 的目的吗?这肯定会违反最小惊讶原则。 programmers.stackexchange.com/questions/187457/…

标签: java multithreading spring session


【解决方案1】:

很明显,线程局部变量(会话)不能共享给 线程,但是它拥有的引用呢?

ThreadLocal 变量在场景后面的Thread 类中放置在一个特殊的映射中,并且该映射维护了设置的值。 map中entry的key是调用线程本身,value是ThreadLocal的“set”方法设置的值。

public class Thread implements Runnable {

..................
..................
/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;


}

现在他们在这里结束的原因是因为在某些时候运行了一段代码(在方法内),它在 Threadlocal 上调用了“set”,它在 threadlocals 映射中创建了一个条目,其中键作为线程,值作为对象集。

public class ThreadLocal<T> {


.........
.........
 /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

}

因此,只要设置的变量没有从线程方法堆栈中逃脱,它对每个线程都是唯一的,因此是线程安全的。但是,如果它指向一些共享变量,例如类的实例变量,那么就有可能多个线程指向 threadlocal 映射中的同一个对象。在那种情况下 - 它绝对不是线程安全的。 Threadlocal 的文档也提到了这一点:

每个线程都包含对其本地线程副本的隐式引用 只要线程处于活动状态并且 ThreadLocal 实例就可以变量 可访问;在一个线程消失后,它的所有线程本地实例副本都将受到垃圾回收(除非其他 存在对这些副本的引用)。

【讨论】:

    【解决方案2】:

    看看Java Atomic Variables

    原子操作要么完成,要么根本不完成。其他线程将无法看到“进行中”的操作。它永远不会在部分完整的状态下被查看。

    【讨论】:

    • 你说的很对,但问题不是what to use?,而是is it works?how it works? :)
    • 最终字段,根据定义,在对象构建后不会更改。如果您真正的意思是您希望该字段设置一次。所以我认为它不会起作用,因为你有一个使用 final 关键字声明的对象的设置器。
    【解决方案3】:

    声明this.object.setId(savedObject.getId());实际上会影响原始对象。因为object不是从其他线程的本地线程中检索到的,而是您直接将其传递给其他线程。

    如果您在第二个线程中调用 Session.getObject();,那么它将返回一个线程特定的对象副本。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-14
      • 2015-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-02
      相关资源
      最近更新 更多