【问题标题】:How to resolve this deadlock?如何解决这个僵局?
【发布时间】:2016-03-18 11:16:05
【问题描述】:

我在代码中添加了 cmets 来解释发生死锁的位置。 基本上,有两个线程。每个线程获取一个Manager对象的锁,然后去获取静态资源的锁,这是应用程序中所有Manager对象的映射。两个线程都调用映射上的get()Manager 类已覆盖 equals() 方法。 equals() 进一步调用了Manager 类的一些同步方法。因此,映射上的get() 将需要对映射中的每个对象一个一个的对象级锁定,直到键匹配,因为 equals 被覆盖。 我只能更改子类中的代码(Sub1Sub2)并避免死锁,因为我无法访问其他类。

编辑:我无权访问 syncMap。 “同步”块中的代码在我调用其 API 的第三方代码中执行。

我可以通过在Manager 上获得锁定finally 来避免这种情况,而不是在尝试阻止之前?!

    public class Parent{
        protected Manager manager;
    }

    public class Global{
        private static final Map syncMap = Collections.synchronizedMap(new HashMap());
        //syncMap contains all the objects of Manager in the application
    }

    class Manager{


        public boolean equals(Object o){

            Manager obj = (Manager)o;
            return obj.getURL().equals(getURL());
        }

        public final synchronized String getURL(){
            return msettings.getDBURL(); //msettings is a global variable
        }


    }


    //Thread-1 is executing someMethod() of this class

    class Sub1 extends Parent{
        Global global;
        //consider manager and Global object are not null
        public void someMethod()
        {
            synchronized(manager){// Thread-1 succesfully takes object level lock on a manager object, say Manager01
                try{
                    global.syncMap.get(manager);
                    // Thread-1 Succesfully takes class level lock on syncMap
                    //  get() calls equals() for each object in syncMap. 
                    //equals() need object lock on each Manager Object in map as it further calls synchronized getURL()
                    // But on one manager Object(Manager02) Thread-2 has already acquired lock and is waiting for lock on syncMap which this thread-1 holds

                }
                finally{
                    manager.releaseConnection();
                }

            }
        }
    }

    //Thread-2 is executing otherMethod() of this class
    class Sub2 extends Parent{ 
        public void otherMethod()
        {
            synchronized(manager){// this takes a lock on manager(Manager02)
                try{
                    global.syncMap.get(manager);
                    // this is blocked as syncMap is aquired by thread-1


                }
                finally{
                    manager.releaseConnection();
                }

            }
        } 
    }

【问题讨论】:

  • 您认为为什么会出现死锁?这段代码应用了带同步(管理器)的有序内在锁,所以我没有看到死锁......
  • 我的雇主组织面临僵局。线程转储说这只发生在这里。线程 1 在 syncMap 上具有类级别锁定,并且正在等待 SyncMap 中可用的 Manager 类实例的锁定。另一个线程已锁定 Manager 对象,正在等待锁定 SyncMap。 Manager 可以有多个实例,所有实例都在 SyncMap 中进行记录。
  • 完全没有必要强制一个设计,其中equals 正在做同步的东西,摆脱它。充其量在某些客户端代码中评估equals 会告诉您,在equals 调用期间 的状态是什么,并且在调用后不提供任何状态的承诺。而是实现原子操作 (trySomeOperation),告诉调用者所需的条件是否符合预期并且操作是否已执行。
  • Sub1 和 Sub2 对象是如何实例化的?他们是否拥有相同的管理器实例?你能描述一下你是如何启动你的线程的吗?
  • Sub1 和 sub2 具有不同的管理器对象。管理器对象由一些我们无权访问的 API 调用返回。 API 通过 Map 跟踪所有管理器对象。为简单起见,我没有显示 API 调用。我通过 java 反编译器查看了 API 代码,并将该代码添加到我的代码的同步块中,以便在此处发布。

标签: java multithreading deadlock java-threads


【解决方案1】:

在获取新信息后,除了将所有处理转换为串行样式外,我没有看到其他解决方案。因此,您可以将所有与管理器相关的 API 调用放在某个包装类的一个同步方法中,并将此包装器用作第三方 API 的单个入口点。

class BrutalWrapper {
    public synchronized void doIt(Manager manager)
    {
        try{
            global.syncMap.get(manager);

        }
        finally{
            manager.releaseConnection();
        }
    }
}

class Sub1 extends Parent{
    BrutalWrapper brutal;
    public void someMethod()
    {
        brutal.doIt(manager);
    }
}

class Sub2 extends Parent{
    BrutalWrapper brutal;
    public void someMethod()
    {
        brutal.doIt(manager);
    }
}

【讨论】:

  • 我无权访问 syncMap。我放在同步块中的代码在我无权访问的 API 调用中执行。我只能访问 Manager 对象
  • 您说您可以更改 Sub1 和 Sub2 类,并且您将 Global 对象显示为 Sub1 类的一部分,并在 Sub1 中调用 global.syncMap.get 方法......您应该更具体那么问题
  • 那么什么类/方法真的可以访问syncMap对象呢?
  • @nukie:您要么需要确保Sub1Sub2 使用BrutalMapper 的相同实例,要么将doIt 方法设为静态。否则这将不起作用...
  • @gustf 实际上我已经提到过:...并将此包装器用作第三方 API 的单个入口点
【解决方案2】:

首先,您确实应该尝试消除equals 方法中的同步需求。它会带来比解决的更多的麻烦,所以如果重新设计是可能的,那么我认为这是最好的方法。

但是,如果您稍微重构代码并将global.syncMap.get(manager) 移动到synchronization 块之前,则不会产生死锁

public Class Parent{
    protected Manager manager;
}

class Global{
    private static final Map syncMap = Collections.synchronizedMap(new HashMap());
    //syncMap contains all the objects of Manager in the application
}

class Manager{


    public boolean equals(Object o){

        Manager obj = (Manager)o;
        return obj.getURL().equals(getURL());
    }

    public final synchronized String getURL(){
        return msettings.getDBURL(); //msettings is a global variable
    }


}


//Thread-1 is executing someMethod() of this class

class Sub1 extends Parent{
    Global global;
    //consider manager and Global object are not null
    public void someMethod()
    {
         try {
             global.syncMap.get(manager);
             synchronized(manager){

             }
         }
         finally{
             manager.releaseConnection();
         }            
    }
}

//Thread-2 is executing otherMethod() of this class
class Sub2 extends Parent{ 
    public void otherMethod()
    {
         try {
             global.syncMap.get(manager);
             synchronized(manager){

             }
         }
         finally{
             manager.releaseConnection();
         }                        
    } 
}

更新替代同步Global.class,也可以使用实例变量global代替Global.class

更新将同步更改为结束Manager.class,而不是Global.class

class Sub1 extends Parent
{
    Global global;

    public void someMethod()
    {
        synchronized (Manager.class) { 
            try {
                global.syncMap.get(manager);
            }
            finally {
                manager.releaseConnection();
            }
        }
    }
}


class Sub2 extends Parent
{
    Global global;

    public void otherMethod()
    {
        synchronized (Manager.class) { 
            try {
                global.syncMap.get(manager);
            }
            finally {
                manager.releaseConnection();
            }
        }
    }
}

【讨论】:

  • 这样做是为了让两个线程处理相同的管理器数据,因此可能会出现新的并发问题:正如我所见,最初的想法是将 manager.releaseConnection() 方法调用放在同步块中。 .
  • 谢谢。我不能改变设计。我只能访问子类。实际的同步块如下所示,它进行 API 调用。在那个 API 里面,所有的坏事都会发生。为了简单起见,我从 API 中提取代码并放入同步块中,同时提出问题。同步(经理){ 尝试 { fdb1Objssert = routedDrug.getFDBClassifications(); } 最后 { manager.returnConnection(); }
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-26
  • 2011-01-09
  • 1970-01-01
  • 2012-02-03
  • 2017-04-10
  • 2011-07-09
  • 2018-04-24
相关资源
最近更新 更多