1. 程式人生 > >java 多執行緒(4) 執行緒同步之鎖(synchronized) / 死鎖 / 兩個鎖定物件期間訪問(修改)其變數的面試

java 多執行緒(4) 執行緒同步之鎖(synchronized) / 死鎖 / 兩個鎖定物件期間訪問(修改)其變數的面試

一. 鎖的定義

鎖就是synchronized 關鍵字,記住synchronized(this )是鎖定當前物件。在函式m1()裡面寫synchronized( this ),這個和public synchronized void m1() 等價。

但是他只鎖定當前物件的synchronized 大括號內的話,其他,這個synchronized 不去鎖定。這個物件的其他方法 / 變數( 被synchronized 語句塊改過後的) 還能被其他執行緒呼叫。鎖定了這個物件,但是不是說完全鎖定了,針對這個方法的鎖定期間,其他方法照樣可以修改變數值。  所以,如果你想把類變數 ( 銀行賬戶值 ) 值控制的完全,不會被錯誤修改,要把所有控制這個變數的方法全部考慮到,該synchronized 要synchronized 。

二. 鎖的粒度

如果是物件變數,鎖this,即只對操作這個物件的眾多執行緒有限制作用,對其他類物件無關,如果是類變數,鎖class。鎖誰,就是拿到誰的鎖,那麼其他需要這個鎖的語句塊,就得排隊,但是不需要這個鎖的語句塊,直接執行不受阻礙。所以就有了鎖obja,鎖objb這種有多把鎖的情況。而不是簡單的只鎖this 。也就是說m1() 要等著鎖的到來才能執行,而m2()不需要等著鎖就能執行( synchronized )。

三. 鎖的程式碼

鎖程式碼1

package test.java.Thread;

public class TestSync implements Runnable{

	Timer timer = new Timer();
	public static void main(String[] args) {
		TestSync test = new TestSync();
		Thread t1 = new Thread(test);
		Thread t2 = new Thread(test);//注意這種用法,Runnable() 引數不為空,則呼叫t1.start() 時候,會呼叫test 的run()方法
		t1.setName("t1");
		t2.setName("t2");
		t1.start();
		t2.start();
	}

	@Override
	public void run() {
		timer.add(Thread.currentThread().getName());
	}

}
class Timer {
	private int num =0;
	public void add(String name){
		synchronized (this) { //很明顯不加這一句,列印結果是錯的,解決辦法就是在執行這7句話的過程中,請你把我當前的物件鎖住 //這裡加this 叫鎖定當前物件
			num++;            //鎖定當前物件的意思是在執行這個大括號裡面的語句之中,一個執行緒執行的過程之中,不會被另外一個執行緒打斷,一個執行緒已經進入到
			try{			  //我這個鎖定的區域裡面了,你放心,不可能有另外一個執行緒也在這裡面,這叫互斥鎖。
				Thread.sleep(1);
			}catch(InterruptedException e){
				
			}
			System.out.println(name+" 你是第"+num+"個使用timer 的執行緒");//t1 你是第2個使用timer 的執行緒     t2 你是第2個使用timer 的執行緒 
		}//end synchronized
	}
//	public synchronized void add(String name){//這個是上面的簡便寫法,意思是在執行這個函式的過程之中,鎖定當前物件
//		.
//		.
//		.
//	}
	
}

死鎖程式碼2

package test.java.Thread;
/**
 * 
 * @author jalo
 *	這是兩個執行緒死鎖,哲學家問題是多個執行緒轉著圈的死鎖
 *  解決死鎖的辦法之一,把鎖的粒度加粗一些,你鎖定一個物件不就行了?非要鎖定裡面的兩個物件(o1,o2),
 *  當然解決死鎖還有很多其他辦法。如果不寫系統級的程式,很難碰到死鎖的問題。因為死鎖都被中介軟體廠商給解決了。
 *  如果有機會寫系統級的程式,比如自己實現一個數據庫的連線池,就會自己實現各種鎖,就會去考慮控制死鎖。
 *
 */
public class TestDeadLock implements Runnable{  
	int flag ;
	static Object o1 = new Object();
	static Object o2 = new Object();
	
	@Override
	public void run() {
		if(flag==0){
			synchronized (o1) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (o2) {
					System.out.println("flag==0");
				}
			}
		}
		else if (flag==1){
			synchronized (o2) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (o1) {
					System.out.println("flag==1");
				}
			}
		}
	}


	public static void main(String[] args) {
		TestDeadLock td1 = new TestDeadLock();
		TestDeadLock td2 = new TestDeadLock();
		td1.flag = 0;
		td2.flag = 1;
		Thread t1 = new Thread(td1);
		Thread t2 = new Thread(td2);
		t1.start();
		t2.start();
	}

}

鎖定物件期間訪問其物件的面試題,程式碼3

package test.java.Thread;
/**
 * 
 * @author jalo
 * 這是個面試題,一個執行緒在呼叫m1()期間,另一個執行緒可以呼叫m2()嗎?如果可以,那麼m2()呼叫的結果是b=0還是b=1000?
 * 可以呼叫,b=1000
 *  synchronized(this)是鎖定當前物件,但是他只鎖定當前物件的synchronized 大括號內的話,
 *  這個物件的其他方法 / 變數,這個synchronized 不去鎖定。這個物件的其他方法 / 變數( 被synchronized 語句塊改過後的) 還能被其他執行緒呼叫。
 */
public class ThreadLockQuestion {
	int b = 0;
	
	public synchronized void m1() throws Exception{
		b = 1000;
		Thread.sleep(1000000000);
		System.out.println("b= "+b);
	}
	
	public void m2(){
		System.out.println(b);
	}
	
	public static void main(String[] args) {
		ThreadLockQuestion tlq = new ThreadLockQuestion();
		ThreadLock tl1 = new ThreadLock(tlq);
		ThreadLock tl2 = new ThreadLock(tlq);
		tl1.flag = 1;
		tl2.flag = 2;
		Thread t1 = new Thread(tl1);
		Thread t2 = new Thread(tl2);
		t1.start();
		t2.start();
	}
}
class ThreadLock implements Runnable{
	ThreadLockQuestion tlq ;
	int flag;
	
	ThreadLock(ThreadLockQuestion tlq){
		this.tlq = tlq;
	}
	
	@Override
	public void run() {
		try {
			if(flag==1){
				tlq.m1();
				System.out.println("flag==1");
			}
			if(flag==2){
				Thread.sleep(1000);
				tlq.m2();  //這裡m2()看到的是1000 , 
				System.out.println("flag==2");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

結果:

1000        //說明是m2()的呼叫結果,而且b 已經被鎖語句塊給改成了1000,並且鎖住物件期間,還能訪問這個物件的變數,訪問結果是最新值
flag==2

............   //一大堆時間過後

b=1000

flag==1

鎖定物件期間修改其變數的面試題,程式碼4

package test.java.Thread;
/**
 * 
 * @author jalo
 * 這是個面試題,一個執行緒在呼叫m1()期間,另一個執行緒可以呼叫m2()嗎?如果可以,那麼m2()呼叫的結果是b=0還是b=1000?
 * 可以呼叫,b=1000
 *  synchronized(this)是鎖定當前物件,但是他只鎖定當前物件的synchronized 大括號內的話,即m1()方法中的語句
 *  鎖定了這個物件,但是不是說完全鎖定了這個物件,針對這個方法m1()的鎖定期間,m2()方法照樣可以修改變數b 的值。
 *  
 */
public class ThreadLockQuestion {
	int b = 0;
	
	public synchronized void m1() throws Exception{
		b = 1000;
		Thread.sleep(5000);
		System.out.println("b= "+b);  //這個是b=2000,意思是雖然m1()被鎖定期間,但是m2()照樣能改b 的值
	}
	
	public void m2() throws Exception{
		Thread.sleep(2500);
		b=2000;
		System.out.println(b);
	}
	
	public static void main(String[] args) {
		ThreadLockQuestion tlq = new ThreadLockQuestion();
		ThreadLock tl1 = new ThreadLock(tlq);
		ThreadLock tl2 = new ThreadLock(tlq);
		tl1.flag = 1;
		tl2.flag = 2;
		Thread t1 = new Thread(tl1);
		Thread t2 = new Thread(tl2);
		t1.start();
		t2.start();
	}
}
class ThreadLock implements Runnable{
	ThreadLockQuestion tlq ;
	int flag;
	
	ThreadLock(ThreadLockQuestion tlq){
		this.tlq = tlq;
	}
	
	@Override
	public void run() {
		try {
			if(flag==1){
				tlq.m1();
				System.out.println("flag==1");
			}
			if(flag==2){
				Thread.sleep(1000);
				tlq.m2();  //這裡m2()看到的是1000 , 
				System.out.println("flag==2");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


結果:

2000  //以下是m2()呼叫結果    

flag==2      
b= 2000    // 以下是m1()呼叫結果,可見m1()鎖定期間,m2()照樣能改b 的值。使用同步要非常小心,你m1()同步了,但是其他執行緒可以自由訪問任意未同步的方法m2(),其訪問可以對你同步的方法m1()產生影響。所以要仔細考慮函式加不加同步,加了同步,導致效率變低,不加同步,有可能導致資料不一致的情況。
flag==1     //

程式碼4改進,程式碼5

如果你想把類變數 ( 銀行賬戶值 ) 值控制的完全,不會被錯誤修改,要把所有控制這個變數的方法全部考慮到,該synchronized 要synchronized。這裡把m2()也改成synchronized 即可。

改了這一句,加了synchronized,public synchronized void m2() throws Exception{

package test.java.Thread;
/**
 * 
 * @author jalo
 * 這是個面試題,一個執行緒在呼叫m1()期間,另一個執行緒可以呼叫m2()嗎?如果可以,那麼m2()呼叫的結果是b=0還是b=1000?
 * 可以呼叫,b=1000
 *  synchronized(this)是鎖定當前物件,但是他只鎖定當前物件的synchronized 大括號內的話,即m1()方法中的語句
 *  鎖定了這個物件,但是不是說完全鎖定了這個物件,針對這個方法m1()的鎖定期間,m2()方法照樣可以修改變數b 的值。
 *  
 */
public class ThreadLockQuestion {
	int b = 0;
	
	public synchronized void m1() throws Exception{
		b = 1000;
		Thread.sleep(5000);
		System.out.println("b= "+b);  //這個是b=2000,意思是雖然m1()被鎖定期間,但是m2()照樣能改b 的值
	}
	
	public synchronized void m2() throws Exception{
		Thread.sleep(2500);
		b=2000;
		System.out.println(b);
	}
	
	public static void main(String[] args) {
		ThreadLockQuestion tlq = new ThreadLockQuestion();
		ThreadLock tl1 = new ThreadLock(tlq);
		ThreadLock tl2 = new ThreadLock(tlq);
		tl1.flag = 1;
		tl2.flag = 2;
		Thread t1 = new Thread(tl1);
		Thread t2 = new Thread(tl2);
		t1.start();
		t2.start();
	}
}
class ThreadLock implements Runnable{
	ThreadLockQuestion tlq ;
	int flag;
	
	ThreadLock(ThreadLockQuestion tlq){
		this.tlq = tlq;
	}
	
	@Override
	public void run() {
		try {
			if(flag==1){
				tlq.m1();
				System.out.println("flag==1");
			}
			if(flag==2){
				Thread.sleep(1000);
				tlq.m2();  //這裡m2()看到的是1000 , 
				System.out.println("flag==2");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}



結果:

b= 1000
flag==1       //由此可見,本來應該m2()呼叫先出結果,但是卻死等m1()結束,說明synchronized m2() 起了作用。
2000
flag==2       //執行緒1,在呼叫 m1() 的執行的過程中,執行緒2要呼叫其他的 synchronized 的函式也會卡死,直到執行緒1執行完,排隊的執行緒2 的函式再執行。

                     //於是,m1(),m2() 這種都是synchronized 的函式,又一樣了,又有順序了,又不能打亂了,無論幾個執行緒執行他們,都是按照順序。誰先呼叫(誰 

                      先拿到this 鎖,這裡是t1 先拿到),誰就執行,執行完,再執行後排隊的,一個個呼叫著走。這其實就是第一行說的和synchronized( this ) 等價,故

                      t1 呼叫 m1() 期間拿到this的鎖,直到 t1 結束,t2 呼叫m2() 才能拿到this 的鎖去執行m2()。這叫從原理上理解。