1. 程式人生 > >Java 多執行緒(二)—— 執行緒的同步

Java 多執行緒(二)—— 執行緒的同步

實現Runnable介面

public class TestThread2 {
    public static void main(String [] args){
        Window window=new Window();
        Thread thread1=new Thread(window,"視窗一");
        Thread thread2=new Thread(window,"視窗二");
        Thread thread3=new Thread(window,"視窗三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Window implements Runnable{ int ticket=50; @Override public void run(){ while (true){ if(ticket > 0){ try { Thread.currentThread().sleep(100);//模擬賣票需要一定的時間 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()
+"售票,票號為:"+ticket--); }else { break; } } } }

執行結果:

視窗二售票,票號為:13
視窗三售票,票號為:12
視窗一售票,票號為:11
視窗二售票,票號為:10
視窗一售票,票號為:10
視窗三售票,票號為:10
視窗三售票,票號為:9
視窗一售票,票號為:8
視窗二售票,票號為:7
視窗三售票,票號為:6
視窗一售票,票號為:5
視窗二售票,票號為:4
視窗三售票,票號為:3
視窗一售票,票號為:2
視窗二售票,票號為:
1 視窗三售票,票號為:0 視窗一售票,票號為:-1

結果分析:這裡出現了票數為0和負數還有重票的情況,這在現實生活中肯定是不存在的,那麼為什麼會出現這樣的情況呢?

  當票號為10時:A執行緒、B執行緒、C執行緒同時進入到if(ticket > 0)的程式碼塊中,A執行緒已經執行了列印輸出語句,但是還沒有做ticket--操作;  這時B執行緒就開始執行了列印操作,那麼就會出現兩個執行緒列印票數一樣,即賣的是同一張票  當票號為1時:A執行緒、B執行緒,C執行緒同時進入到if(ticket > 0)的程式碼塊中,A執行緒執行了列印語句,並且已經做完了ticket--操作,則此時ticket=0;  B執行緒再列印時就出現了0的情況,同理C執行緒列印就會出現-1的情況。

解決辦法:即我們不能同時讓超過兩個以上的執行緒進入到 if(ticket > 0)的程式碼塊中,不然就會出現上述的錯誤。必須讓一個執行緒操作共享資料完畢以後,其他執行緒才有機會參與共享資料的操作。我們可以通過以下兩個辦法來解決:

  1、使用 同步程式碼塊

  2、使用 同步方法

使用 同步程式碼塊

synchronized(同步監視器){
      //需要被同步的程式碼塊(即為操作共享資料的程式碼)
}

  同步監視器:由任意一個類的物件來充當,哪個執行緒獲取此監視器,誰就執行大括號裡被同步的程式碼。俗稱:鎖

要求:1、所有的執行緒必須公用同一把鎖!不能相對於執行緒是變化的物件;

        2、並且只需鎖住操作共享資料的程式碼,鎖多了或少了都不行;

例項:

1、實現的方式

public class TestWindow {
    public static void main(String [] args){
        Window1 window=new Window1();
        Thread thread1=new Thread(window,"視窗一");
        Thread thread2=new Thread(window,"視窗二");
        Thread thread3=new Thread(window,"視窗三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Window1 implements  Runnable{
    int ticket=100;//共享資料
    @Override
    public void  run(){
        while (true){
            synchronized (this){//this表示當前物件,此時表示建立的 window
                if(ticket > 0){
                    try {
                        //模擬賣票需要一定的時間
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket--);
                }
            }
        }
    }
}

注意:在實現的方式中,考慮同步的話,可以使用this充當鎖,但在繼承的方式中,會建立多個物件,慎用this

2、繼承的方式

public class TestWindow1 {
    public static void main(String [] args){
        Window2 window1=new Window2();
        Window2 window2=new Window2();
        window1.start();
        window2.start();
    }
}


class Window2 extends Thread{
    static int ticket=100;//共享資料;注意宣告為 static,表示幾個視窗共享
    static Object object=new Object();//用static 可以表示唯一
    @Override
    public void  run(){
        while (true){
            //synchronized (this){//this表示當前物件,此時表示建立的 window1和window2
            synchronized (object){//鎖必須是唯一,不能每個執行緒都使用自己的一把鎖
                if(ticket > 0){
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket--);
                }
            }
        }
    }
}

注意:1、繼承的方式會建立多個例項,所以共享資源需要用static來修飾,表示共享

      2、繼承的方式會建立多個例項,所以 this 表示不同的例項物件,這裡表示widow1和window2,所以不能使用this當鎖,此時可以定義一個 static 修飾的物件當鎖

使用 同步方法

語法:即用  synchronized  關鍵字修飾方法

將操作共享資料的方法宣告為synchronized。即此方法為同步方法,能夠保證當其中一個執行緒執行此方法時,其他執行緒再外等待直至此執行緒執行完此方法。注意:同步方法的鎖:this

例項:

1、實現的方式

public class TestWindow2 {
    public static void main(String [] args){
        Window3 window=new Window3();
        Thread thread1=new Thread(window,"視窗一");
        Thread thread2=new Thread(window,"視窗二");
        Thread thread3=new Thread(window,"視窗三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Window3 implements  Runnable{
    int ticket=100;//共享資料
    @Override
    public void  run(){
        while (true){
            show();
        }
    }

    public synchronized void show(){//this充當鎖,此時表示建立的 window;
        // 如果用繼承的方式,使用同步方法,這裡表示建立的 window1和window2,繼承的方式不要使用同步方法
        if(ticket > 0){
            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket--);
        }
    }
}

注意:1、synchronized 的鎖為this,這裡表示建立的物件例項window;

     2、繼承的時候t this 表示建立的window1和window2,繼承的方式不要使用同步方法。