Java併發編程--加鎖機制初步,內置鎖以及內置鎖的重入

其實這一節要說的重點又是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();
	}
}

關於第二點,主要是要理解概念,重入,即reentrantWhen 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的兩個重要特色已經講完,至於synchronizedLock的性能,以及synchronized底層實現原理,將在後面的章節中講述。其實講到這裏基本是能完成JDK1.5以前的併發任務了,雖然還有不少能夠由算法實現的原子操做或者鎖,可是synchronized仍是用起來最方便的。只是,你們被它的重量級給嚇壞。好在1.6以後就有所改觀了。

固然後面還有更好的類和方法會介紹。關於併發有太多太多的內容能夠寫。我也一直有一個疑問,底層究竟是怎麼作事務回滾的?有相關的代碼實現,仍是在硬件層作的。很好奇。

謝謝觀賞。
相關文章
相關標籤/搜索