【问题标题】:Changes in Java 6 synchronization?Java 6 同步的变化?
【发布时间】:2016-03-04 16:21:27
【问题描述】:

我正在查看一些在 Java 6 及更高版本中导致问题(死锁)的代码,但在 Java 1.5 中却没有。

BMP 豆:

private MyClass m_c;
public String ejbCreate(String id) throws CreateException, MyException
{
    try
    {
        m_c = Singleton.getInstance().getObj(id);
    }
    catch (MyException e)
    {
        synchronized (Singleton.getInstance())
        {
            //check again
            if (!Singleton.getInstance().hasObj(id)) {
                m_c = new MyClass(id);
                Singleton.getInstance().addObj(id, m_c);
            }
            else {
                m_c = Singleton.getInstance().getObj(id);
            }
        }
    }
}

单例:

private Map objCache = new HashMap();
private static Singleton INSTANCE = new Singleton();
public static Singleton getInstance() {
    return INSTANCE;
}
public void addObj(String id, MyClass o)
{
    if (this.objCache.containsKey(id)) {
        this.objCache.remove(id);
    }
    this.objCache.put(id, o);
}

public MyClass getObj(String id) throws Exception
{
    MyClass o = null;
    o = (MyClass)this.objCache.get(id);
    if (o == null) {
        throw new MyException("Obj " +id+ " not found in cache");
    }
    return o;
}

public boolean hasObj(String id)
{
    return this.objCache.containsKey(id);
}

到目前为止的经验证据表明,在整个 try/catch 中进行同步可以解决使用 Java 6 时的死锁。

显然可以有一个或多个线程调用

Singleton.getInstance().getObj(id) 

没有获得锁,而另一个线程拥有锁并正在执行同步块中的代码,但即使在考虑了 JSR-133 中详述的内存同步之后,在这种情况下看起来也不应该有任何问题。

我知道我没有解释问题是什么,只是说这是一个僵局,只画一小部分并不理想,但要画整个画面会占用很大的画布。

我查看了 Java 6 版本的注释,唯一听起来相关的领域是无竞争同步,但我不知道这在这种情况下是否重要。

感谢您的帮助。

【问题讨论】:

  • 要获得死锁,您需要尝试在不同的线程中同时获得两个或更多锁。单独一把锁不会死锁。您能否展示使用其他锁以及如何以不同的顺序获得这些锁?
  • 注意:如果您以不安全的方式更新地图,可能会进入无限循环。这一直存在。我会考虑让整个代码线程安全,如果您需要并发访问,请使用 ConcurrentMap(在 Java 5.0 中添加)
  • 顺便说一句,如果你有一个真正的死锁,并且这一切都来自使用内置的synchronized 关键字,那么线程转储实际上会检测到死锁并告诉你它。如果你没有看到,这是彼得理论的有力证据。
  • 这是有症状的。您正在一个接一个地执行几个不必要的哈希查找,但试图通过双重检查锁定反模式来提高性能。看addObj;它确实if(map.containsKey(key)) { map.remove(key); } map.put(key, …);,执行三个哈希查找来完成单个map.put(key, …); 所做的事情。或者,当密钥不存在时,ejbCreate 会发生什么,首先是未同步的get,然后是同步的 hasObjcontainsKey),然后是 addObj(另一个 containsKey,然后是 @ 987654334@) 总共进行了四次查找...
  • 这段代码肯定是由于双重检查锁定而被破坏的,但是,涉及两个锁的死锁不会发生在只有一个锁的一段代码中,但是代码显然不是你真正的代码,因为它甚至没有编译,由于未处理/未声明的异常和缺少返回语句等。

标签: java multithreading jvm synchronization jdk1.6


【解决方案1】:

我怀疑您没有遇到死锁(在以不同顺序获得的两个不同线程中持有两个锁),而是进入了无限循环。如果您以非线程安全的方式访问 HashMap,则可能会发生这种情况。用于处理冲突的链表中发生的事情似乎会自行恢复,并且阅读器会永远运行。这一直是个问题,尽管 Java 6 中的一些细微差别可能会在不同版本可能不会出现此问题时出现。

我建议你修复这个类,让它使用线程安全的集合,而不是重试异常,因为不能保证这会发生。

你可以做很多事情来改进这个类,但你真正需要的是在 Java 8 中添加的 ConcurrentMap.computeIfAbsent。

注意:没有理由

  • 在尝试删除之前检查密钥是否存在。
  • 在尝试插入之前将其移除。
  • 抛出异常而不是返回 null。
  • 当您可以将其传递给工厂时返回 null。 (根据 computeIfAbsent)
  • 在预先知道类型时使用工厂。

我建议你

  • 使用 ConcurrentMap 进行线程安全的并发访问。
  • enum 用于单例。

这两个都是在 Java 5.0 中添加的。

public enum MyClassCache {
    INSTANCE;

    private final Map<String, MyClass> cache = new ConcurrentHashMap<>();

    public boolean hasId(String id) {
        return cache.containsKey(id);
    }

    public MyClass get(String id) throws IllegalStateException {
        MyClass ret = cache.get(id);
        if (ret == null) throw new IllegalStateException(id);
        return ret;
    }

    public MyClass getOrCreate(String id) throws IllegalStateException {
        MyClass ret = cache.get(id);
        if (ret == null) {
            synchronized (cache) {
                ret = cache.get(id);
                if (ret == null) {
                    cache.put(id, ret = new MyClass(id));
                }
            }
        }
        return ret;
    }
}

在 Java 8 中,您可以使用 computeIfAbsent

public MyClass getOrCreate(String id)  {
    return cache.computeIfAbsent(id, MyClass::new);
}

【讨论】:

  • 嗨,彼得。谢谢你的回复。这绝对是我们看到的死锁 - org.jboss.util.deadlock.ApplicationDeadlockException。我们查看了线程,这确实是一个典型的死锁 - 线程 1 有 A 并且想要 B,线程 2 有 B 并且想要 A。
  • @Paul 你能告诉我们两个死锁的代码路径吗?
  • @Paul:在您显示的代码中,只涉及一个锁,因此如果涉及两个锁的死锁,您的问题充其量是不完整的。
【解决方案2】:

我说得对吗,这个问题的核心是:

public void ejbCreate1(String id) throws Exception {
    try {
        m_c = Singleton.getInstance().getObj(id);
    } catch (Exception e) {
        synchronized (Singleton.getInstance()) {
            //check again
            if (!Singleton.getInstance().hasObj(id)) {
                m_c = new MyClass(id);
                Singleton.getInstance().addObj(id, m_c);
            } else {
                m_c = Singleton.getInstance().getObj(id);
            }
        }
    }
}

public void ejbCreate2(String id) throws Exception {
    synchronized (Singleton.getInstance()) {
        try {
            m_c = Singleton.getInstance().getObj(id);
        } catch (Exception e) {
            //check again
            if (!Singleton.getInstance().hasObj(id)) {
                m_c = new MyClass(id);
                Singleton.getInstance().addObj(id, m_c);
            } else {
                m_c = Singleton.getInstance().getObj(id);
            }
        }
    }
}

在 Java-6 中,这会导致第一个挂起而第二个正常工作。

显然主要区别在于getObj 可能同时被两个不同的线程调用,甚至可能在另一个线程创建新对象时被调用

来自Is it safe to get values from a java.util.HashMap from multiple threads (no modification)?,您可能不在这种情况下。结论是一个线程从Map(可能是o = (MyClass) this.objCache.get(id);)读取,而另一个线程通过调用addObj 写入映射。这显然是阅读崩溃和燃烧的秘诀。

有关潜在天坑的详细信息,请参阅Is a HashMap thread-safe for different keys?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多