【问题标题】:Understanding synchronized blocks了解同步块
【发布时间】:2014-06-27 09:30:41
【问题描述】:

我不明白为什么使用synchronized(syncObject) 比使用synchronized(this) 更好。比如这个类:

public class Pool implements ObjectPool {
    private Object[] pool;
    private int initialCapacity;
    private int available = 0;
    private int waiting = 0;
    private final Object syncObject = new Object();

    public Pool(int initialCapacity) {
        this.initialCapacity = initialCapacity;
        pool = new Object[initialCapacity]; 
    }

    public void releaseObject(Object o) throws Exception {
        synchronized (syncObject) {
            pool[available] = o;
            available++;
        }

        if (waiting > 0) {
            notify();
        }
    }
}

【问题讨论】:

标签: java multithreading synchronize


【解决方案1】:

因为如果你使用this,那么另一个试图执行另一个方法的线程将不得不等待,而如果你使用一个对象作为锁,你只限制这个临界区。

【讨论】:

  • 如果所有方法都在this 上同步,那么进入任何方法的第一个线程将获得锁,并且所有其他线程都必须等待,无论发生什么,直到第一个线程释放它的锁。
【解决方案2】:

如果您使用synchronized(this),任何引用您的对象的代码都可以通过synchronized(yourObject) 与您的同步进行交互。这可能会产生意想不到的副作用或鼓励其他开发人员使用 synchronized(this) 编写依赖于您的代码的代码。

通过使用synchronized(myLockObj) 而myLockObj 是您的对象中私有的对象,其他任何人都无法在同一个对象上进行同步。因此,与您的锁定没有交互,并且您的类之外的代码对您强制执行线程安全的方式没有依赖性。换句话说,您可以在以后更改您的实现,而不会破坏您的类之外的其他代码。


举个例子,Hashtable 是一个类,其中所有方法都是 synchronized 上的 Hashtable 本身。所以以下工作:

Hashtable t;
…
synchronized(t) {
  if(!t.containsKey(k)) t.put(k,v);
}

由于这是有保证的,因此永远无法更改实现。

相比之下,ConcurrentHashMap 不提供任何从外部锁定地图的可能性。所以你必须使用提供的putIfAbsent 方法来实现类似的。这允许未来对实现进行改进以提高吞吐量,事实上,这种底层改进已经完成。

【讨论】:

    【解决方案3】:

    您必须了解为什么要使用同步。您这样做是为了确保没有数据竞争。如果一个对象的一个​​字段只有一个数据竞争是可能的,并且有几个这样的竞争,那么在整个对象上同步是没有意义的,因为你会减慢执行速度。考虑这段代码:

    class SynchTestThis {
      Collection col1 = new ArrayList();
      Collection col2 = new ArrayList();
    
      public void addCol1(Object obj) {
        synchronized(this) {
          col1.add(obj);
        }
      }
    
      public void addCol2(Object obj) {
        synchronized(this) {
          col2.add(obj);
        }
      }
    }
    
    class SynchTestObj {
      Collection col1 = new ArrayList();
      Collection col2 = new ArrayList();
    
      public void addCol1(Object obj) {
        synchronized(col1) {
          col1.add(obj);
        }
      }
    
      public void addCol2(Object obj) {
        synchronized(col2) {
          col2.add(obj);
        }
      }
    
    }
    

    如果SynchTestThis 同时向两个集合添加元素是不可能的。在SynchTestObj的情况下可以做到。

    换句话说,选择要同步的对象是正确识别和保护关键部分的问题。

    【讨论】:

      【解决方案4】:

      正如其他答案中所指出的:通过在this 上同步,您将暴露您锁定的对象。

      但要再次指出为什么这可能是一个问题:它可能导致死锁。例如,想象一下这两个方法由两个不同的线程执行:

      private final Object localMonitor = new Object();
      private final Pool pool = new Pool(); 
      
      void methodA()
      {
          synchronized (localMonitor)
          {
              pool.releaseObject(null);
          }
      }
      
      void methodB()
      {
          synchronized (pool)
          {
              synchronized (localMonitor)
              {
                  System.out.println("Performing some work...");
              }
          }
      }
      

      第一个线程将在localMonitor 上同步,然后尝试从Pool 类调用方法。 第二个线程将在pool 实例上进行同步,然后尝试在localMonitor 上进行同步。

      就其本身而言,此代码是“有效的”。这实际上会很好。 除非来自Pool 类的方法也是同步的。然后,线程将陷入死锁。这种情况可以通过使用专用的syncObject来避免。

      这里再次说明,作为一个可运行的示例:只需通过切换注释行将对象更改为 synchronized 以查看差异。

      class Pool 
      {
          private final Object syncObject = new Object();
          public void releaseObject(Object o) 
          {
              //synchronized (syncObject) // <----------- This will work 
              synchronized (this) //      // <----------- This will cause a deadlock
              {
                  System.out.println("Modify pool");
              }
          }
      }
      
      class SimpleSynchronizeExample
      {
          public static void main(String[] args)
          {
              SimpleSynchronizeExample s = new SimpleSynchronizeExample();
              s.start();
          }
      
          private final Object localMonitor = new Object();
          private final Pool pool = new Pool(); 
      
          void methodA()
          {
              synchronized (localMonitor)
              {
                  try { Thread.sleep(100); } catch (Exception e) {}
                  pool.releaseObject(null);
              }
          }
      
          void methodB()
          {
              synchronized (pool)
              {
                  try { Thread.sleep(100); } catch (Exception e) {}
                  synchronized (localMonitor)
                  {
                      System.out.println("Performing some work...");
                  }
              }
          }
      
      
      
          private void start()
          {
              Thread tA = new Thread(new Runnable() 
              {
                  @Override
                  public void run()
                  {
                      methodA();
                  }
              });
              Thread tB = new Thread(new Runnable()
              {
                  @Override
                  public void run()
                  {
                      methodB();
                  }
              });
      
              tA.start();
              tB.start();
      
              try
              {
                  tA.join();
                  tB.join();
              }
              catch (InterruptedException e)
              {
                  e.printStackTrace();
              }
              System.out.println("Done");
          }
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多