其實這一節要說的重點又是synchronized,由於和內置鎖最相關的應該就是synchronized了。固然咱們仍是會從一些例子開始講起。樣例程序仍是來自那本書: java
@NotThreadSafe public class UnsafeCachingFactorizer implements Servlet { private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>(); private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>(); public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber.get())) encodeIntoResponse(resp, lastFactors.get() ); else { BigInteger[] factors = factor(i); lastNumber.set(i); lastFactors.set(factors); encodeIntoResponse(resp, factors); } } }
這個程序很明顯是一個線程不安全的類,而程序想要告訴咱們的是這樣一件事:儘管lastNumber和lastFactors都是線程安全的類,並且它們的方法是原子性的,是線程安全的,可是,若是將它們組合在一塊兒,就破壞了原子性,就是不安全的。這也告誡咱們,不要隨意的去拼湊兩個原子性操做。 算法
解決的方法很簡單,在方法前加修飾符synchronized。固然這不是一個好辦法,雖然能解決問題,可是它卻帶來了性能上的問題。每個要執行service方法的線程,必需要等到前一個線程執行完了service,才能執行。固然,稍微好點兒的辦法是寫成synchronized塊。 安全
@ThreadSafe public class CachedFactorizer implements Servlet { @GuardedBy("this") private BigInteger lastNumber; @GuardedBy("this") private BigInteger[] lastFactors; @GuardedBy("this") private long hits; @GuardedBy("this") private long cacheHits; public synchronized long getHits() { return hits; } public synchronized double getCacheHitRatio() { return (double) cacheHits / (double) hits; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = null; synchronized (this) { ++hits; if (i.equals(lastNumber)) { ++cacheHits; factors = lastFactors.clone(); } } if (factors == null) { factors = factor(i); synchronized (this) { lastNumber = i; lastFactors = factors.clone(); } } encodeIntoResponse(resp, factors); } }
值得注意的是,若是使用了synchronized塊包圍了的對象或者操做,儘可能使用最簡單純粹的變量(避免使用原子對象)。這裏涉及到一個鎖的嵌套的問題,這個內容會在後續的章節中講述,如今只要記住一點:避免鎖的嵌套。 併發
而今天的主題是爲何這樣作了,程序就變得Thread Safe了,synchronized的原理究竟是什麼,底層究竟是怎麼實現的。Every Java object can implicitly act as a lock for purposes of synchronization; these built-in locks are called intrinsic locks or monitor locks。內置鎖或者監視器鎖。內置鎖是一種互斥鎖,同一時刻只容許一個線程執行內置鎖保護的代碼。因此一旦程序執行到synchronized鎖包括的代碼時,就會去拿內置鎖,若是內置鎖沒有被別的線程佔用,則拿到鎖,待執行完相關代碼後,釋放鎖;若是內置鎖已經被別的線程佔用,則等待。 ide
上述的概念都容易理解,只不過還要注意兩點,第一,內置鎖對那些沒有加synchronized修飾符的方法是不起做用的,線程仍是能照樣訪問到;第二,內置鎖是可重入的。 性能
針對第一點,咱們看下面的程序就能理解: 測試
package com.a2.concurrency.chapter1; /** * 主要驗證內置鎖的做用域和做用時間 * * @author ChenHui * */ public class TestSynchronized { private int i = 0; public synchronized void add() { System.out.println("excute add()"); i++; try { Thread.sleep(2000); System.out.println("after excute add()..."); } catch (Exception e) { System.err.print("error"); } } public synchronized void sub() { System.out.println("excute sub()..."); i--; } public void print() { try { Thread.sleep(1000); } catch (Exception e) { } System.out.println("value of i:" + i); } public static void main(String[] args) throws Exception { final TestSynchronized ts = new TestSynchronized(); Thread th1 = new Thread("th1") { public void run() { System.out.println("Thread:" + super.getName()); ts.add(); } }; final Thread th2 = new Thread("th2") { public void run() { System.out.println("THread:" + super.getName()); /**改變ts.sub()和ts.print()執行順序,就能體會出內置鎖對print方法是不上鎖的*/ ts.sub(); ts.print(); // ts.sub(); } }; th1.start(); Thread.sleep(1000); th2.start(); } }
關於第二點,主要是要理解概念,重入,即reentrant。When a thread requests a lock that is already held by another thread, the requesting thread blocks. But because intrinsic locks are reentrant, if a thread tries to acquire a lock that it already holds, the request succeeds.注意這個it already holds,這個指的是,若是當前線程已經由它本身持有的鎖。看代碼: ui
public class Widget { public synchronized void doSomething() { ... } } public class LoggingWidget extends Widget { public synchronized void doSomething() { System.out.println(toString() + ": calling doSomething"); super.doSomething(); } }
上述代碼,若是子類調用doSomething方法時,得到自身類鎖的同時會得到父類的鎖,若是內置鎖不可重入,那調用super.doSomething時就會發生死鎖。因此重入就是,你拿了父類的鎖,再調用該鎖包含的代碼能夠不用再次拿鎖。注意,/**重入始終是發生在一個線程中的-->這句話有問題改成-->重入是針對同一個引用的*/重入是針對同一個引用的。 this
下面的代碼主要是爲了證實:得到自身類鎖的同時會得到父類的鎖。 spa
package com.a2.concurrency.chapter1; /** * 主要爲了測試,子類覆蓋父類方法後,調用子類方法時,也獲取父類的鎖 * * @author ChenHui * */ public class TestWidget { public static void main(String[] args) throws InterruptedException { final LoggingWidget widget = new LoggingWidget(); Thread th1 = new Thread("th1") { @Override public void run() { System.out.println(super.getName() + ":start\r\n"); widget.doSometing(); } }; Thread th2 = new Thread("th2") { @Override public void run() { System.out.println(super.getName() + ":start\r\n"); /** 爲了說明子類複寫父類方法後,調用時也持有父類鎖*/ widget.doAnother(); /**證實了內置鎖對那些沒有加synchronized修飾符的方法是不起做用的*/ // widget.doNother(); /**爲了說明子類複寫父類方法後,調用時也持有父類鎖,也持有本身本類的鎖*/ // widget.doMyLike(); /**這是兩個線程,這是須要等待的,並非繼承的關係,不是重入,重入是發生在一個線程中的*/ // widget.doSometing(); } }; th1.start(); Thread.sleep(1000); th2.start(); } } class Widget { public synchronized void doSometing() { System.out.println("widget ... do something..."); } public synchronized void doAnother() { System.out.println("widget... do another thing..."); } public void doNother() { System.out.println("widget... do Nothing..."); } } class LoggingWidget extends Widget { @Override public synchronized void doSometing() { try { System.out.println("loggingwidget do something..."); Thread.sleep(3000); System.out.println("end loggingwidget do something..."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } super.doSometing(); } public synchronized void doMyLike() { System.out.println("loggingwidget do my like..."); } }
至此,synchronized的兩個重要特色已經講完,至於synchronized和Lock的性能,以及synchronized底層實現原理,將在後面的章節中講述。其實講到這裏基本是能完成JDK1.5以前的併發任務了,雖然還有不少能夠由算法實現的原子操做或者鎖,可是synchronized仍是用起來最方便的。只是,你們被它的重量級給嚇壞。好在1.6以後就有所改觀了。
固然後面還有更好的類和方法會介紹。關於併發有太多太多的內容能夠寫。我也一直有一個疑問,底層究竟是怎麼作事務回滾的?有相關的代碼實現,仍是在硬件層作的。很好奇。
謝謝觀賞。