3

我试图了解 Struts2 ScopeInterceptor 类(/org/apache/struts2/interceptor/ScopeInterceptor.java)内部是否存在线程安全问题,这是有问题的代码:

    private static Map locks = new IdentityHashMap();

static final void lock(Object o, ActionInvocation invocation) throws Exception {
    synchronized (o) {
        int count = 3;
        Object previous = null;
        while ((previous = locks.get(o)) != null) {
            if (previous == invocation) {
                return;
            }
            if (count-- <= 0) {
                locks.remove(o);
                o.notify();

                throw new StrutsException("Deadlock in session lock");
            }
            o.wait(10000);
        }
        ;
        locks.put(o, invocation);
    }
}

static final void unlock(Object o) {
    synchronized (o) {
        locks.remove(o);
        o.notify();
    }
}

我有一个 Websphere 应用程序显示 45 个停滞的线程,CPU 使用率很高。33 个线程在“解锁”方法内的“locks.remove(o)”处停止。其他 12 个线程在 "lock" 方法内的 "locks.get(o)" 内停止。

在我看来,IdentityHashMap 的使用是线程不安全的。可以简单地用 Collections.synchronizedMap() 包装 IdentityHashMap 解决这个问题吗?:

    private static Map locks = Collections.synchronizedMap(new IdentityHashMap());

static final void lock(Object o, ActionInvocation invocation) throws Exception {
    synchronized (o) {
        int count = 3;
        Object previous = null;
        while ((previous = locks.get(o)) != null) {
            if (previous == invocation) {
                return;
            }
            if (count-- <= 0) {
                locks.remove(o);
                o.notify();

                throw new StrutsException("Deadlock in session lock");
            }
            o.wait(10000);
        }
        ;
        locks.put(o, invocation);
    }
}

static final void unlock(Object o) {
    synchronized (o) {
        locks.remove(o);
        o.notify();
    }
}

在我看来,作者试图通过使用同步代码块来“修复”IdentityHashMap 的同步问题,但是如果对象“o”是特定于线程的对象,这并不能防止多线程。而且,由于 lock 和 unlock 中的代码块是分开的,因此 IdentityHashMap 将(并且确实!)被多个线程同时调用(根据我们的 Java 核心证据)。

Collections.synchronizedMap() 包装器是正确的修复,还是我遗漏了什么?

4

3 回答 3

0

没有真正的答案,但希望有一些有用的信息:

IdentityHashMap 文档 ( http://docs.oracle.com/javase/7/docs/api/java/util/IdentityHashMap.html ) 指出:

请注意,此实现不是同步的。如果多个线程同时访问一个身份哈希图,并且至少有一个线程在结构上修改了该图,则必须在外部进行同步。(结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的键关联的值不是结构修改。)这通常通过在自然封装映射的某个对象上同步来完成. 如果不存在这样的对象,则应使用 Collections.synchronizedMap 方法“包装”地图。这最好在创建时完成,以防止对地图的意外不同步访问:

地图 m = Collections.synchronizedMap(new IdentityHashMap(...));

所以 Collections.synchronizedMap 策略听起来是对的,但是这个页面(http://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html)让我想知道它是否会起作用,因为这些方法是静态的:

同步方法中的锁

当线程调用同步方法时,它会自动获取该方法对象的内在锁,并在方法返回时释放它。即使返回是由未捕获的异常引起的,也会发生锁定释放。

您可能想知道调用静态同步方法时会发生什么,因为静态方法与类相关联,而不是与对象相关联。在这种情况下,线程获取与该类关联的 Class 对象的内在锁。因此,对类的静态字段的访问由与类的任何实例的锁不同的锁控制。

由于这些是静态方法(即使字段不是静态的),很难判断 Collections.synchronizedMap 包装器是否真的可以防止死锁......我的答案是我没有答案!

于 2013-09-25T03:06:23.147 回答
0

我相信您是对的,并且似乎存在线程安全问题。开发人员试图通过在对象“o”上同步来实现线程安全,但看起来这个对象实际上是会话对象,而不是范围更广的对象。我相信更改需要在锁定对象上进行同步。

于 2013-09-25T11:08:40.463 回答
0

是的,我想是这样。如果您使用不同的操作系统访问 lock(Object o, ActionInvocation invocation),您将同时使用不同线程的不同监视器修改 IdentityHashMap。这使得不同线程可以同时调用 IdentityHashMap。

这可以通过同步 IdentityHashMap 来解决。

于 2013-09-28T19:22:54.840 回答