4、線程--線程同部
線程同部
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方法
synchronized關鍵字可以作為函數的修飾符,也可作為函數內的語句,也就是平時說的同步方法和同步語句塊。
如果再細的分類,synchronized可作用於instance變量、object reference(對象引用)、
static函數和class literals(類名稱字面常量)身上。
明確幾點:
A.無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是對象
而不是把一段代碼或函數當作鎖――而且同步方法很可能還會被其他線程的對象訪問。
B.每個對象只有一個鎖(lock)與之相關聯。
C.實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。
1、把synchronized當作函數修飾符時
public synchronized voidtest() { ... }
這也就是同步方法,那這時synchronized鎖定的是哪個對象呢?
它鎖定的是調用這個同步方法對象。也就是說,當一個對象1在不同的線程中執行這個同步方法時
它們之間會形成互斥,達到同步的效果。
但是這個對象所屬的Class所產生的另一對象P2卻可以任意調用這個被加了synchronized關鍵字的方法。
等同於上述的代碼:
public void test() { synchronized (this) { } }
這裏的this指的是調用這個對方法的對象
同部方法的實質是將synchronized作用於object reference
哪個拿到了對象1對象鎖的線程,才可以調用對象1的同部方法
對於對象2來說,對象1這個鎖與其毫不相幹,程序也可能在這種情況下擺脫同部機制的控制
造成數據混亂
2、同部塊
public void method3(SomeObject so){ synchronized(so) { //….. } }
此時的鎖就是so這個對象
誰拿到這個鎖誰就可以運行它所控制的那段代碼
當有一個明確的對象作為鎖時,就寫成這樣的程序
但沒有明確的對象作為鎖,只是想讓一段代碼同部
可以創建一個特殊的instance變量來充當鎖
class Foo implements Runnable { private byte[] lock = new byte[0]; // 特殊的instance變量 Public void methodA() { synchronized(lock) { //… } } //….. }
零長度的byte數組對象創建起來將比任何對象都經濟――查看編譯後的字節碼:生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。
3、將synchronuzed作用於static函數
Class Foo{ public synchronized static void methodAAA() // 同步的static 函數 { //…. } public void methodBBB() { synchronized(Foo.class) // class literal(類名稱字面常量) } }
代碼中的methodBBB()方法是把class literal作為鎖的情況
它和同步的static函數產生的效果是一樣的,取得的鎖很特別
是當前調用這個方法的對象所屬的類(Class,而不再是由這個Class產生的某個具體對象了)。
可以推斷:如果一個類中定義了一個synchronized的static函數A
也定義了一個synchronized 的instance函數B
那麽這個類的同一對象Obj在多線程中分別訪問A和B兩個方法時
不會構成同步,因為它們的鎖都不一樣。A方法的鎖是Obj這個對象
而B的鎖是Obj所屬的那個Class。
總結:
1、線程同步的目的是為了保護多個線程反問一個資源時對資源的破壞。
2、線程同步方法是通過鎖來實現,每個對象都有切僅有一個鎖,這個鎖與一個特定的對象關聯,線程一旦獲取了對象鎖,其他訪問該對象的線程就 無法再訪問該對象的其他非同步方法
3、對於靜態同步方法,鎖是針對這個類的,鎖對象是該類的Class對象。靜態和非靜態方法的鎖互不幹預。
一個線程獲得鎖,當在一個同步方法中訪問另外對象上的同步方法時,會獲取這兩個對象鎖。
4、對於同步,要時刻清醒在哪個對象上同步,這是關鍵。
5、編寫線程安全的類,需要時刻註意對多個線程競爭訪問資源的邏輯和安全做出正確的判斷,
對“原子”操作做出分析,並保證原子操作期間別的線程 無法訪問競爭資源。
6、當多個線程等待一個對象鎖時,沒有獲取到鎖的線程將發生阻塞。
7、死鎖是線程間相互等待鎖鎖造成的,在實際中發生的概率非常的小。
真讓你寫個死鎖程序,不一定好使。但是,一旦程序發生死鎖,程序將死掉。
實戰小測試拿蘋果的測試
蘋果的數量是固定的,定義兩個線程分別去去數據並進行打印
public class testTongBu implements Runnable{ private int apple = 5; //拿蘋果 boolean getApple(){ //將整個執行的代碼鎖起來 //此每個線程只能單獨的訪問 synchronized(this){ if(apple > 0){ System.out.println(Thread.currentThread().getName() + "拿走了一個apple"); apple--; try { Thread.sleep(1500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("還剩" + apple); return true; } return false; } } @Override public void run() { boolean flag = getApple(); while(flag){ flag = getApple(); } System.out.println("apple 沒了"); } public static void main(String[] args) { testTongBu t = new testTongBu(); Thread t1 = new Thread(t); t1.setName("xiaoming"); Thread t2 = new Thread(t); t2.setName("xiaoqiang"); t1.start(); t2.start(); //多線程應用程序同時訪問共享對象時,由於線程間相互搶占CPU的控制權, //造成一個線程夾在另一個線程的執行過程中運行, //所以可能導致錯誤的執行結果。 } }
4、線程--線程同部