1. 程式人生 > >多執行緒基礎4 同步與通訊

多執行緒基礎4 同步與通訊

 1.什麼情況下需要同步 當多執行緒併發執行同一程式碼時 希望某一段程式碼執行的過程中CPU不要切換到其他執行緒工作. 這時就需要同步. 


 2.同步程式碼塊 使用synchronized關鍵字加上一個鎖物件來定義一段程式碼, 這就叫同步程式碼塊 多個同步程式碼塊如果使用相同的鎖物件, 那麼他們就是同步的

 


public class TestMain {
    public static void print1() {
        //鎖物件可以是任意物件,但是被鎖的程式碼需要保證是同一把鎖,不能用匿名物件
        synchronized(String.class){
            System.out.print("1");
            System.out.print("2");
        }
    }

    //非靜態同步函式的鎖是:this   (非靜態方法已建立例項化物件後呼叫的)
    public synchronized void print2() {
        System.out.print("1");
        System.out.print("2");
    }

   // 靜態的同步函式的鎖是:位元組碼物件.class   (靜態是初始化時形成的所以 是位元組碼物件)
    public static synchronized void print3() {
        System.out.print("1");
        System.out.print("2");
    }

}

 

多執行緒(兩個執行緒間的通訊)

  • 1.什麼時候需要通訊 * 多個執行緒併發執行時, 在預設情況下CPU是隨機切換執行緒的 * 如果我們希望他們有規律的執行, 就可以使用通訊, 例如每個執行緒執行一次列印
  • 2.怎麼通訊 * 如果希望執行緒等待, 就呼叫wait() * 如果希望喚醒等待的執行緒, 就呼叫notify(); * 這兩個方法必須在同步程式碼中執行, 並且使用同步鎖物件來呼叫

 

public class Demo1_Notify {



    /**

     * @param args

     * 等待喚醒機制

     */

    public static void main(String[] args) {

        final Printer p = new Printer();



        new Thread() {

            public void run() {

                while(true) {

                    try {

                        p.print1();

                    } catch (InterruptedException e) {



                        e.printStackTrace();

                    }

                }

            }

        }.start();



        new Thread() {

            public void run() {

                while(true) {

                    try {

                        p.print2();

                    } catch (InterruptedException e) {



                        e.printStackTrace();

                    }

                }

            }

        }.start();

    }



}



//等待喚醒機制

class Printer {

    private int flag = 1;

    public void print1() throws InterruptedException {

        synchronized(this) {

            if(flag != 1) {

                this.wait();               //當前執行緒等待

            }

            System.out.print("方法一");

            System.out.print("\r\n");

            flag = 2;

            this.notify();                //隨機喚醒單個等待的執行緒

        }

    }



    public void print2() throws InterruptedException {

        synchronized(this) {

            if(flag != 2) {

                this.wait();

            }

            System.out.print("方法2");

            System.out.print("\r\n");

            flag = 1;

            this.notify();

        }

    }

}

 

多執行緒(三個或三個以上間的執行緒通訊)

多個執行緒通訊的問題 * notify()方法是隨機喚醒一個執行緒 * notifyAll()方法是喚醒所有執行緒 * JDK5之前無法喚醒指定的一個執行緒 * 如果多個執行緒之間通訊, 需要使用notifyAll()通知所有執行緒, 用while來反覆判斷條件

public class Demo2_NotifyAll {



    /**

     * @param args

     */

    public static void main(String[] args) {

        final Printer2 p = new Printer2();

        new Thread() {

            public void run() {

                while(true) {

                    try {

                        p.print1();

                    } catch (InterruptedException e) {



                        e.printStackTrace();

                    }

                }

            }

        }.start();



        new Thread() {

            public void run() {

                while(true) {

                    try {

                        p.print2();

                    } catch (InterruptedException e) {



                        e.printStackTrace();

                    }

                }

            }

        }.start();



        new Thread() {

            public void run() {

                while(true) {

                    try {

                        p.print3();

                    } catch (InterruptedException e) {



                        e.printStackTrace();

                    }

                }

            }

        }.start();

    }



}


1,在同步程式碼塊中,用哪個物件鎖,就用哪個物件呼叫wait方法

2,為什麼wait方法和notify方法定義在Object這類中?

     因為鎖物件可以是任意物件,Object是所有的類的基類,所以wait方法和notify方法需要定義在Object這個類中

3,sleep方法和wait方法的區別?

  a,sleep方法必須傳入引數,引數就是時間,時間到了自動醒來wait方法可以傳入引數也可以不傳入引數,傳入引數就是在引數的時間結束後等待,不傳入引數就是直接等待

  b,sleep方法在同步函式或同步程式碼塊中,不釋放鎖,睡著了也抱著鎖睡wait方法在同步函式或者同步程式碼塊中,釋放鎖

 

class Printer2 {

    private int flag = 1;

    public void print1() throws InterruptedException {

        synchronized(this) {

            while(flag != 1) {

                this.wait();               //當前執行緒等待

            }

            System.out.print("方法一");

         

            System.out.print("\r\n");

            flag = 2;

            //this.notify();                  //隨機喚醒單個等待的執行緒

            this.notifyAll();

        }

    }



    public void print2() throws InterruptedException {

        synchronized(this) {

            while(flag != 2) {

                this.wait();               //執行緒2在此等待

            }

            System.out.print("方法2");

             System.out.print("\r\n");

            flag = 3;

            //this.notify();

            this.notifyAll();

        }

    }



    public void print3() throws InterruptedException {

        synchronized(this) {

            while(flag != 3) {

                this.wait();                  //執行緒3在此等待,if語句是在哪裡等待,就在哪裡起來

                //while迴圈是迴圈判斷,每次都會判斷標記

            }

            System.out.print("方法三");

            System.out.print("\r\n");

            flag = 1;

            //this.notify();

            this.notifyAll();

        }

    }

}

 

消費者與生產者


//消費者
synchronized(物件) {

		if(貨物==null)
		物件.wait();
	
	    對應的處理邏輯
}

//生產者

synchronized(物件) {
	產生貨物
	物件.notifyAll();
}

執行緒間的通訊還可以使用 管道輸入/輸出流

public class Piped {
    public static void main(String[] args) throws Exception {
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();
// 將輸出流和輸入流進行連線,否則在使用時會丟擲IOException
        out.connect(in);
        Thread printThread = new Thread(new Print(in), "PrintThread");
        printThread.start();
        int receive = 0;
        try {
            while ((receive = System.in.read()) != -1) {
                out.write(receive);
            }
        } finally {
            out.close();
        }
    }

    static class Print implements Runnable {
        private PipedReader in;

        public Print(PipedReader in) {
            this.in = in;
        }

        public void run() {
            int receive = 0;
            try {
                while ((receive = in.read()) != -1) {
                    System.out.print((char) receive);
                }
            } catch (IOException ex) {
            }
        }
    }
}