理解 java 中的 synchronized 關鍵字

實際上,我關於java的基礎知識的90%以上都來自Thinking in Java。對於其中的synchronized關鍵字,
當時就是瀏覽一下,大概知道意思,也沒有細看。後來一直沒有用過這個關鍵字。
昨天看Thinking in Patterns with Java中的Observer模式,
看了其中的Observable類的源碼,發現裏面幾乎全部的方法都用了synchronized關鍵字(不是所有),
其中個別用了synchronized(this){}的區塊。因而,我發覺有必要好好理解一下這個關鍵字了。 

我再次看了侯捷譯的Thinking in Java第二版中文版,獲得有關synchronized的以下信息: 

一、synchronized關鍵字的做用域有二種: 
1) 是某個對象實例內,synchronized aMethod(){}能夠防止多個線程同時訪問這個對象的synchronized方法
    (若是一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不
    能同時訪問這個對象中任何一個synchronized方法)。這時,不一樣的對象實例的synchronized方法是不相干擾的。
    也就是說,其它線程照樣能夠同時訪問相同類的另外一個對象實例中的synchronized方法; 
2) 是某個類的範圍,synchronized static aStaticMethod{}防止多個線程同時訪問這個類中的synchronized static 方法。
    它能夠對類的全部對象實例起做用。 

二、除了方法前用synchronized關鍵字,synchronized關鍵字還能夠用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。
    用法是: synchronized(this){/*區塊*/},它的做用域是當前對象; 
    
三、synchronized關鍵字是不能繼承的,也就是說,基類的方法synchronized f(){} 在繼承類中並不自動是synchronized f(){},
    而是變成了f(){}。繼承類須要你顯式的指定它的某個方法爲synchronized方法; 

四、疑問: 
    我知道了有static區塊(其中的加載時機好像也不清楚,須要學習一下,原來看Thinking in Java好像是說: static區塊加載的時機
    是類的一個對象實例建立或類的一個static方法被訪問,可是我今天早上看了javaeye.com上面的一個關於「<static塊到底何時執行?
    -eway -JavaEye技術社區>」的文章後又有點迷糊了:),也許,須要認真學習一下JVM的內在機制纔是理解這類問題最根本的途徑),
    也有synchronized區塊,那麼,有synchronized static 區塊嗎?意思是問:有沒有一個類做用域內的synchronized區塊?

今天,寫了下面這個例子驗證了上面有沒有一個類範圍的synchronized 或 synchronized static 塊,結論是沒有這樣的塊。也就是,對整個 類範圍的某塊資源實行synchronized,惟一的方法是用:synchronized static方法,像這樣:synchronized static void ff() {/*your code here*/} (littlebat後注:2007.2.13, 在朋友V.C的幫助下,終於知道在類的一個方法中,能夠存在整個類範圍內的 synchronized區塊,像這樣:        synchronized (TestSynchronized.class) {/*some code*/},詳情見後 )  所有的例子以下,若是個人例子和想法有任何不當的地方,還但願看見的朋友指點一下。謝謝。 java

例程:  TestSynchronized.java併發

public class TestSynchronized {

	static {
		System.out.println("Step 1: a static block in class scope.");
	}

	static void f() {
		System.out.println("Step 2: a static method f() in class scope.");
	}

	synchronized static void ff() { // The only way of synchronized a part in
									// class scope.
		System.out
				.println("Step 3: a synchronized static method ff() in class scope.");
	}

	/*
	 * //No any synchronized block in class scope! synchronized {
	 * System.out.println("A synchronized block in class scope."); }
	 */

	/*
	 * //No any synchronized static block in class scope! synchronized static {
	 * System.out.println("A synchronized static block in class scope."); }
	 */

	void fff() {
		synchronized (this) {
			System.out
					.println("Step 4: a synchronized block in method fff() in object scope.");
		}
	}

	synchronized void ffff() {
		System.out
				.println("Step 5: a synchronized method ffff() in object scope.");
	}

	public static void main(String[] args) {

		TestSynchronized.f();
		TestSynchronized.ff();
		(new TestSynchronized()).fff();
		(new TestSynchronized()).ffff();

	}

}

運行結果:
Step 1: a static block in class scope. 
Step 2: a static method f() in class scope. 
Step 3: a synchronized static method ff() in class scope. 
Step 4: a synchronized block in method fff() in object scope. 
Step 5: a synchronized method ffff() in object scope.

The format below can implement synchronizing in Class scope: 

synchronized (TestSynchronized.Class) { 
}


Yes, I can get synchronized block in a method in class scope 
(Note: TestSynchronized.class isn't TestSynchronized.Class):

void fffff(){ 
        synchronized (TestSynchronized.class) { 
            System.out.println("Step 6: a synchronized block in method fffff() in class scope."); 
        } 
    }
  函數

------------------------------------------------   
對synchronized(this)的一些理解  :
------------------------------------------------   
synchronized 關鍵字,它包括兩種用法:synchronized 方法和 synchronized 塊。
1. synchronized 方法

經過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。如:
public synchronized void accessVal(int newVal);
synchronized 方法控制對類成員變量的訪問:每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,
不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能得到該鎖,從新進入可執行狀態。
這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(由於至多隻有一個可以
得到該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要全部可能訪問類成員變量的方法均被聲明爲 synchronized)。
在 Java 中,不光是類實例,每個類也對應一把鎖,這樣咱們也可將類的靜態成員函數聲明爲 synchronized ,以控制其對類的靜態成員變量的
訪問。
synchronized 方法的缺陷:若將一個大的方法聲明爲synchronized 將會大大影響效率,典型地,若將線程類的方法 run() 聲明爲 
synchronized ,因爲在線程的整個生命期內它一直在運行,所以將致使它對本類任何 synchronized 方法
oop

的調用都永遠不會成功。固然咱們能夠
經過將訪問類成員變量的代碼放到專門的方法中,將其聲明爲 synchronized ,並在主方法中調用來
學習

解決這一問題,可是 Java 爲咱們提供了更好的解決辦法,那就是 synchronized 塊。

2. synchronized 塊

經過 synchronized關鍵字來聲明synchronized 塊。語法以下: 
synchronized(syncObject) {
//容許訪問控制的代碼
}
synchronized 塊是這樣一個代碼塊,其中的代碼必須得到對象 syncObject (如前所述,
ui

能夠是類實例或類)的鎖方能執行,具體機制同前所述。因爲能夠針對任意代碼塊,this

且可任意指定上鎖的對象,故靈活性較高。spa

舉例說明以下:.net

1、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,線程

一個時間內只能有一個線程獲得執行。
另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。

public class Thread1 implements Runnable {
	public void run() {
		synchronized (this) {
			for (int i = 0; i < 5; i++) {
				System.out.println(Thread.currentThread().getName()
						+ " synchronized loop " + i);
			}
		}
	}

	public static void main(String[] args) {
		Thread1 t1 = new Thread1();
		Thread ta = new Thread(t1, "A");
		Thread tb = new Thread(t1, "B");
		ta.start();
		tb.start();
	}
}

   結果:
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
 

2、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,

另外一個線程仍然能夠訪問該object中的非  synchronized(this)同步代碼塊。

public class Thread2 {
	public void m4t1() {
		synchronized (this) {
			int i = 5;
			while (i-- > 0) {
				System.out
						.println(Thread.currentThread().getName() + " : " + i);
				try {
					Thread.sleep(500);
				} catch (InterruptedException ie) {
				}
			}
		}
	}

	public void m4t2() {
		int i = 5;
		while (i-- > 0) {
			System.out.println(Thread.currentThread().getName() + " : " + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException ie) {
			}
		}
	}

	public static void main(String[] args) {
		final Thread2 myt2 = new Thread2();
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				myt2.m4t1();
			}
		}, "t1");
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				myt2.m4t2();
			}
		}, "t2");
		t1.start();
		t2.start();
	}
}

結果:

t1 : 4
t2 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0

3、尤爲關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,

其餘線程對object中全部其它 synchronized(this)同步代碼塊的訪問將被阻塞。

//修改Thread2.m4t2()方法:

public void m4t2() {
		synchronized (this) {
			int i = 5;
			while (i-- > 0) {
				System.out
						.println(Thread.currentThread().getName() + " : " + i);
				try {
					Thread.sleep(500);
				} catch (InterruptedException ie) {
				}
			}
		}

	}

結果:

t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0

4、第三個例子一樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個

synchronized(this)同步代碼塊時, 它就得到了這個object的對象鎖。

結果,其它線程對該object對象全部同步代碼部分的訪問都被暫時阻塞。
//修改Thread2.m4t2()方法以下:

public synchronized void m4t2() {
		int i = 5;
		while (i-- > 0) {
			System.out.println(Thread.currentThread().getName() + " : " + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException ie) {
			}
		}
	}

結果:

t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0

5、以上規則對其它對象鎖一樣適用:

public class Thread3 {
	class Inner {
		private void m4t1() {
			int i = 5;
			while (i-- > 0) {
				System.out.println(Thread.currentThread().getName()
						+ " : Inner.m4t1()=" + i);
				try {
					Thread.sleep(500);
				} catch (InterruptedException ie) {
				}
			}
		}

		private void m4t2() {
			int i = 5;
			while (i-- > 0) {
				System.out.println(Thread.currentThread().getName()
						+ " : Inner.m4t2()=" + i);
				try {
					Thread.sleep(500);
				} catch (InterruptedException ie) {
				}
			}
		}
	}

	private void m4t1(Inner inner) {
		synchronized (inner) { // 使用對象鎖
			inner.m4t1();
		}
	}

	private void m4t2(Inner inner) {
		inner.m4t2();
	}

	public static void main(String[] args) {
		final Thread3 myt3 = new Thread3();
		final Inner inner = myt3.new Inner();
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				myt3.m4t1(inner);
			}
		}, "t1");
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				myt3.m4t2(inner);
			}
		}, "t2");
		t1.start();
		t2.start();
	}
}

結果:
儘管線程t1得到了對Inner的對象鎖,但因爲線程t2訪問的是同一個Inner中的非同步部分。

因此兩個線程互不干擾。
t1 : Inner.m4t1()=4
t2 : Inner.m4t2()=4
t1 : Inner.m4t1()=3
t2 : Inner.m4t2()=3
t1 : Inner.m4t1()=2
t2 : Inner.m4t2()=2
t1 : Inner.m4t1()=1
t2 : Inner.m4t2()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=0

如今在Inner.m4t2()前面加上synchronized:

private synchronized void m4t2() {
		int i = 5;
		while (i-- > 0) {
			System.out.println(Thread.currentThread().getName()
					+ " : Inner.m4t2()=" + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException ie) {
			}
		}
	}

結果:
儘管線程t1與t2訪問了同一個Inner對象中兩個絕不相關的部分,但由於t1先得到了對Inner的對象鎖,

因此t2對Inner.m4t2()的訪問也被阻塞,由於m4t2()是Inner中的一個同步方法。
t1 : Inner.m4t1()=4
t1 : Inner.m4t1()=3
t1 : Inner.m4t1()=2
t1 : Inner.m4t1()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=4
t2 : Inner.m4t2()=3
t2 : Inner.m4t2()=2
t2 : Inner.m4t2()=1
t2 : Inner.m4t2()=0  

 

Refer:

[1] Java線程同步:synchronized鎖住的是代碼仍是對象

http://blog.csdn.net/xiao__gui/article/details/8188833

[2] [Java] 方法鎖、對象鎖和類鎖的意義和區別

https://yq.aliyun.com/articles/52851#

相關文章
相關標籤/搜索