了解同步块

时间:2014-06-27 09:30:41

标签: java multithreading synchronize

我有理解为什么使用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();
        }
    }
}

4 个答案:

答案 0 :(得分:2)

因为如果你使用this,那么另一个尝试执行另一个方法的线程将不得不等待,而如果你使用一个对象作为锁,你只能限制这个关键部分。

答案 1 :(得分:2)

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

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


举个例子,Hashtablesynchronized本身所有方法都为Hashtable的类。以下是有效的:

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

由于这是有保证的,因此实施永远不会改变。

相反,ConcurrentHashMap没有提供从外部锁定地图的任何可能性。因此,您必须使用提供的putIfAbsent方法来实现类似的功能。这允许将来对实现进行改进以提高吞吐量,事实上,已经进行了这样的改进。

答案 2 :(得分:1)

您必须了解使用同步的原因。您这样做是为了确保没有数据争用。如果对象的一个​​单个字段只能进行单个数据竞争,并且有几个这样的竞争,则整个对象上没有点同步,因为您减慢了执行速度。请考虑以下代码:

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,则可以完成。

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

答案 3 :(得分:1)

正如其他答案所指出:通过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");
    }

}