是否需要嵌套同步块?

时间:2012-11-03 10:24:45

标签: java synchronization nested

我正在编写一个多线程程序,我有一个嵌套同步块的方法,我想知道我是否需要内部同步,或者只是外同步是否足够好。

public class Tester {

    private BlockingQueue<Ticket> q = new LinkedBlockingQueue<>();
    private ArrayList<Long> list = new ArrayList<>();

    public void acceptTicket(Ticket p) {
        try {
            synchronized (q) {
                q.put(p);

                synchronized (list) {
                    if (list.size() < 5) {
                        list.add(p.getSize());
                    } else {
                        list.remove(0);
                        list.add(p.getSize());
                        }
                }
            }
        } catch (InterruptedException ex) {
            Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}

编辑: 这不是一个完整的课程,因为我仍在努力。但基本上我试图模仿一台售票机。售票机维护BlockingQueue q中的票证列表。每当客户端向机器添加票证时,机器也会跟踪最后5张票证(ArrayList列表)的价格。所以我不相信我需要内部同步,因为它只是这个类(票证系统)将访问list

2 个答案:

答案 0 :(得分:4)

危险:

    synchronized (q) {
        synchronized (list) {
         }
    }

有一天你会添加一个方法,或按照这样的顺序调用方法,实际上它也会这样做:

    synchronized (list) {
        synchronized (q) {
         }
    }

然后它只是一个死锁时间炸弹。

假设这是完整的类,您可以锁定一个,或者通常,在方法上使用synchronized锁定对象本身。对这些私人的所有其他访问也需要同步。

public class Tester {

private BlockingQueue<Ticket> q = new LinkedBlockingQueue<>();
private ArrayList<Long> list = new ArrayList<>();

public synchronized void acceptTicket(Ticket p) {
    try {
      q.put(p);

      if (list.size() < 5) {
        list.add(p.getSize());
      } else {
        list.remove(0);
        list.add(p.getSize());
      }
    } catch (InterruptedException ex) {
        Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
    }    
}

或清洁:

public class Tester {

private BlockingQueue<Ticket> q = new LinkedBlockingQueue<>();
private ArrayList<Long> list = new ArrayList<>();

public void acceptTicket(Ticket p) {
    try {
      //this is cleaner, because I don't know what logger class is doing,
      //I want to eliminate chance of deadlock and reduce time we are in lock
      synchronized (this){
        q.put(p);

        if (list.size() < 5) {
          list.add(p.getSize());
        } else {
          list.remove(0);
          list.add(p.getSize());
        }
     }
    } catch (InterruptedException ex) {
        Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
    }    
}

清洁工仍然,但在你的情况下不太可能是必要的(基于我所看到的):

public class Tester {

private final Object lockObj = new Object(); //specific object for locking
                                //could use any other private, non-exposed final but
                                //this makes it absolutely clear what I should be
                                //using for locks
private BlockingQueue<Ticket> q = new LinkedBlockingQueue<>();
private ArrayList<Long> list = new ArrayList<>();

public void acceptTicket(Ticket p) {
    try {
      //"this" can be locked by external code outside my control here,
      //so I use a specific private final object lockObj to eliminate deadlocks and
      //provide finer grained locking - reducing contension
      synchronized (lockObj){
        q.put(p);

        if (list.size() < 5) {
          list.add(p.getSize());
        } else {
          list.remove(0);
          list.add(p.getSize());
        }
     }
    } catch (InterruptedException ex) {
        Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
    }    
}

答案 1 :(得分:1)

上述代码的目的似乎是在原子操作中对所有操作(队列中的存储和列表中的存储)进行分组。所以是的,外部同步块就是您所需要的。一次保持多个锁是最终导致死锁的好方法,因此应尽可能避免使用。

我不会将队列本身用作锁,而是使用专用的最终锁定对象。

最重要的是:上面的代码没有任何意义,因为列表和队列不会在其他任何地方使用。所以我刚才所说的可能是真的,或者完全错误,这取决于实际代码的其余部分。