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()。這叫從原理上理解。