//: concurrency/CriticalSection.java // Synchronizing blocks instead of entire methods. Also // demonstrates protection of a non-thread-safe class // with a thread-safe one. //package concurrency; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.*; class Pair { // Not thread-safe private int x, y; public Pair(int x, int y) { this.x = x; this.y = y; } public Pair() { this(0, 0); } public int getX() { return x; } public int getY() { return y; } public void incrementX() { x++; } public void incrementY() { y++; } public String toString() { return "x: " + x + ", y: " + y; } public class PairValuesNotEqualException extends RuntimeException { public PairValuesNotEqualException() { super("Pair values not equal: " + Pair.this); } } // Arbitrary invariant -- both variables must be equal: public void checkState() { if(x != y) throw new PairValuesNotEqualException(); } } // Protect a Pair inside a thread-safe class: abstract class PairManager { AtomicInteger checkCounter = new AtomicInteger(0); protected Pair p = new Pair(); private List<Pair> storage = Collections.synchronizedList(new ArrayList<Pair>()); public synchronized Pair getPair() { // Make a copy to keep the original safe: return new Pair(p.getX(), p.getY()); } // Assume this is a time consuming operation protected void store(Pair p) { storage.add(p); try { TimeUnit.MILLISECONDS.sleep(50); } catch(InterruptedException ignore) {} } public abstract void increment(); } // Synchronize the entire method: class PairManager1 extends PairManager { public synchronized void increment() { p.incrementX(); p.incrementY(); store(getPair()); } } // Use a critical section: class PairManager2 extends PairManager { public void increment() { Pair temp; synchronized(this) { p.incrementX(); p.incrementY(); temp = getPair(); } store(temp); } } class PairManipulator implements Runnable { private PairManager pm; public PairManipulator(PairManager pm) { this.pm = pm; } public void run() { while(true) pm.increment(); } public String toString() { return "Pair: " + pm.getPair() + " checkCounter = " + pm.checkCounter.get(); } } class PairChecker implements Runnable { private PairManager pm; public PairChecker(PairManager pm) { this.pm = pm; } public void run() { while(true) { pm.checkCounter.incrementAndGet(); pm.getPair().checkState(); //此处可能没有互斥,getPair()使用的是synchronized提供的对象锁,其他锁不起作用 } } } public class CriticalSection { // Test the two different approaches: static void testApproaches(PairManager pman1, PairManager pman2) { ExecutorService exec = Executors.newCachedThreadPool(); PairManipulator pm1 = new PairManipulator(pman1), pm2 = new PairManipulator(pman2); PairChecker pcheck1 = new PairChecker(pman1), pcheck2 = new PairChecker(pman2); exec.execute(pm1); exec.execute(pm2); exec.execute(pcheck1); exec.execute(pcheck2); try { TimeUnit.MILLISECONDS.sleep(500); } catch(InterruptedException e) { System.out.println("Sleep interrupted"); } System.out.println("pm1: " + pm1 + "\npm2: " + pm2); System.exit(0); } public static void main(String[] args) { PairManager pman1 = new PairManager1(), pman2 = new PairManager2(); testApproaches(pman1, pman2); } } /* Output: (Sample) pm1: Pair: x: 15, y: 15 checkCounter = 272565 pm2: Pair: x: 16, y: 16 checkCounter = 3956974 *///:~
//: concurrency/ExplicitCriticalSection.java // Using explicit Lock objects to create critical sections. //package concurrency; import java.util.concurrent.locks.*; // Synchronize the entire method: class ExplicitPairManager1 extends PairManager { private Lock lock = new ReentrantLock(); public Pair getPair() { //覆盖基类方法,统一使用Lock锁 // Make a copy to keep the original safe: lock.lock(); try { return new Pair(p.getX(), p.getY()); } finally { lock.unlock(); } } public synchronized void increment() { lock.lock(); try { p.incrementX(); p.incrementY(); store(getPair()); } finally { lock.unlock(); } } } // Use a critical section: class ExplicitPairManager2 extends PairManager { private Lock lock = new ReentrantLock(); public void increment() { Pair temp; lock.lock(); //与对象pm2的getPair()中持有锁的方式不同 try { p.incrementX(); p.incrementY(); temp = getPair(); } finally { lock.unlock(); } store(temp); } } public class ExplicitCriticalSection { public static void main(String[] args) throws Exception { PairManager pman1 = new ExplicitPairManager1(), pman2 = new ExplicitPairManager2(); CriticalSection.testApproaches(pman1, pman2); } } /* Output: (Sample) pm1: Pair: x: 15, y: 15 checkCounter = 174035 pm2: Pair: x: 16, y: 16 checkCounter = 2608588 *///:~
明确下synchronized的几个关键点: A.无论synchronized关键字加在方法上还是对象上,他取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。 B.每个对象只有一个锁(lock)和之相关联。 C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。 synchronized可以加在方法上,也可以加在对象上,通常理解为,只有持有了锁才可以进行对应代码块的执行。 java.util.concurrent.locks包下面提供了一些锁的实现,有读写锁,公平锁等。 将synchronized替换成lock的实现可以提升性能: 1. 大部分应用场景是读写互斥,写和写互斥,读和读不互斥。而synchronized则是都互斥。 可以利用读写锁来优化性能,读锁锁住读的代码块,写锁锁住写的代码块。 2. 要确保你在理解原来利用到synchronized的代码逻辑,避免一概而论地把synchronized替换成锁。 public void getPair(){ return "x="+x + ",y="+y; } //这个函数 public synchronized void increment() { x++; y++; getPair(); } //可以替换成 public void increment() { lock.lock(); try{ x++; y++; getPair(); }finally{ lock.unlock(); } //但是,如果getPair()是synchronized public synchronized void getPair(){ return "x="+x + ",y="+y; } //还能替换这个函数吗? public synchronized void increment() { x++; y++; getPair(); } //这时候就不能简单地使用lock来替换了,这里要调用getPair();必需申请到对象锁,这个时候increment也要竞争这把锁 //因此这里的代码效果是读写互斥。 //如果只是用lock来锁住increment,则达不到效果。还得同时锁getPair(); //这里嵌套了synchronized,而synchronized的嵌套结构中在同一个对象的方法上是共享一把锁的。 //上面只是简单的例子,java编程思想的多线程编程中有更详细的这个例子,有兴趣的可以看看。 }