1. 程式人生 > >JAVA多線程提高二:傳統線程的互斥與同步&傳統線程通信機制

JAVA多線程提高二:傳統線程的互斥與同步&傳統線程通信機制

ons 傳統 面試題 content 之前 來看 結束 ktr IV

本文主要是回顧線程之間互斥和同步,以及線程之間通信,在最開始沒有juc並發包情況下,如何實現的,也就是我們傳統的方式如何來實現的,回顧知識是為了後面的提高作準備。

一、線程的互斥

為什麽會有線程的互斥?可以想銀行取款的問題,如果不做監控,多個人同時針對一個存折取錢的時候就會出現錢不對的問題,
下面我們通過兩個例子來分析一下線程的互斥問題以及為什麽會產生這個線程?

例子1:一個人生產信息,一個人消費信息

面向對象的思想:類 信息類 生產者 消費者

public class TriditionalThreadSafeLxh {
    public static void main(String[] args) {
        
// 多個線程調用同一個對象,會帶來數據錯亂線程,產生的原因分析: // 多個線程調用一個對象的某個方法 Info info = new Info(); Production p = new Production(info); Consumer c = new Consumer(info); new Thread(p).start(); new Thread(c).start(); } } class Info { private String name = "李興華";
private String content = "講師"; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
class Production implements Runnable { private Info info; public Production(Info info) { this.info = info; } @Override public void run() { for (int i = 0; i < 20; i++) { if (i % 2 == 0) { this.info.setName("李興華"); try { Thread.sleep(80); } catch (InterruptedException e) { e.printStackTrace(); } this.info.setContent("講師"); }else{ this.info.setName("mldn"); try { Thread.sleep(80); } catch (InterruptedException e) { e.printStackTrace(); } this.info.setContent("www.mldn.cn"); } } } } class Consumer implements Runnable{ private Info info; public Consumer(Info info) { this.info = info; } @Override public void run() { for (int i = 0; i < 20; i++) { try { Thread.sleep(80); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.info.getName()+","+this.info.getContent()); } } }

輸出:

mldn,講師
李興華,www.mldn.cn
mldn,講師
李興華,www.mldn.cn
mldn,講師
李興華,www.mldn.cn
mldn,講師
李興華,www.mldn.cn
mldn,講師
李興華,www.mldn.cn
mldn,講師
李興華,www.mldn.cn
mldn,講師
李興華,www.mldn.cn
mldn,講師
李興華,www.mldn.cn
mldn,講師
李興華,www.mldn.cn
mldn,講師
mldn,www.mldn.cn

例子2:多個線程同時打印名字

public class TraditionalThreadSafe {
    
    public static void main(String[] args) {
        new TraditionalThreadSafe().init();
    }
    
    public void init(){
        final OutputMessage c = new OutputMessage();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(120);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.output("aaaaa");
            }
        }).start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(120);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.output("bbbbb");
            }
        }).start();
        
    }
    
    class OutputMessage{
        public void output(String name){
            while(true){
                for (int i = 0; i < name.length(); i++) {
                    System.out.print(name.charAt(i));//打印一個人的名字
                }
                System.out.println();
            }
        }
    }
}

輸出結果:

bbbbb
bbbbb
aaaa
aabbbbb
bbbbb
bbbbb

通過上面兩個例子我們分析得出線程互斥問題產生的原因:
(1)多個線程調用同一個對象的某個方法
(2)在線程的run()方法中使用sleep更好的顯示出數據錯亂線程

2.現在已經產生了互斥問題 怎麽解決上面的問題? 既然產生的原因是在調用這個對象的方法時候造成互斥,那麽我就在這個方法或者代碼塊實現同步 下面用例子2看一下解決的辦法: 代碼:
public class TraditionalThreadSafe {
    
    public static void main(String[] args) {
        new TraditionalThreadSafe().init();
    }
    
    public void init(){
        final OutputMessage c = new OutputMessage();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(120);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.output("aaaaa");
            }
        }).start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(120);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.output("bbbbb");
            }
        }).start();
        
    }
    
    class OutputMessage{
        public synchronized void output(String name){
//            synchronized (this) {
                while(true){
                    for (int i = 0; i < name.length(); i++) {
                        System.out.print(name.charAt(i));//打印一個人的名字
                    }
                    System.out.println();
                }
//            }
            
        }
    }
}

運行結果:

bbbbb
bbbbb
bbbbb
bbbbb
bbbbb
3.分析一下使用Synchronized在不同地方的使用 (1)同步代碼塊調用的是當前對象 (2)在方法上使用同步調用的當前對象 (3)在靜態static方法上使用同步調用的類.class,因為靜態方法是類直接調用,鎖就是這個類的字節碼

二、線程的通信機制

在講解線程通信機制之前,我們來看一道面試題:子線程循環10次,接著主線程循環100,接著又回到子線程循環10次, 接著再回到主線程又循環100,如此循環50次

分析:

1、主進程子進程存在線程同步問題,對於同步的內容應該封裝在一個類中,在類中定義主進程和子進程需要操作的方法,通過獲得鎖而執行各自的方法。
2、這裏存在子進程和主進程交替運行,應該添加一個信號變量,主進程和子進程判斷該狀態是否可以執行,主進程或子進程一次循環結束重置該變量值,然後調用notify(notifyallAll)方法來喚醒其他等待共享對象釋放的線程。

實現:

定義一個類Business,定義主進程和子進程執行的方法,主進程和子進程對同一個對象business操作,通過使用synchronized作用在該對象相應的方法 從而達到同步的作用。類Business中定義一個交換變量bShouldSub來使得進程交替執行,主進程或子進程執行完都會更改該變量的值,然後調用this.notify 來喚醒其他等待對象business(this)的線程。

public class TraditionalThreadCommucation {
    public static void main(String[] args) {
        final Business b = new Business();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <=50; i++) {
                    b.sub(i);
                }
            }
        }).start();
        
        
        for (int i = 1; i <=50; i++) {
            b.main(i);
        }
    }
}
class Business{
    private  boolean isShouldbeSub=true;        //標記為 判斷是否該子線程運行
    public synchronized void sub(int i){
        while(!isShouldbeSub){  //不該子線程運行的時候進來了
            try {
                this.wait();  //等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 10; j++) {
            System.out.println("子線程循環 : "+j+",外層循環: "+i);
        }
        isShouldbeSub=false;
        this.notifyAll();
    }
    
    public synchronized void main(int i){
        while(isShouldbeSub){    //不該主線程運行的時候進來了,檢查
            try {
                this.wait(); //等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 100; j++) {
            System.out.println("主線程循環 : "+j+",外層循環: "+i);
        }
        isShouldbeSub=true;
        this.notifyAll();
    }
}

運行結果:

子線程循環 : 1,外層循環: 1
子線程循環 : 2,外層循環: 1
子線程循環 : 3,外層循環: 1
子線程循環 : 4,外層循環: 1
子線程循環 : 5,外層循環: 1
子線程循環 : 6,外層循環: 1
子線程循環 : 7,外層循環: 1
子線程循環 : 8,外層循環: 1
子線程循環 : 9,外層循環: 1
子線程循環 : 10,外層循環: 1
主線程循環 : 1,外層循環: 1
主線程循環 : 2,外層循環: 1
主線程循環 : 3,外層循環: 1
主線程循環 : 4,外層循環: 1
主線程循環 : 5,外層循環: 1
主線程循環 : 6,外層循環: 1
主線程循環 : 7,外層循環: 1
主線程循環 : 8,外層循環: 1
主線程循環 : 9,外層循環: 1
主線程循環 : 10,外層循環: 1
主線程循環 : 11,外層循環: 1
主線程循環 : 12,外層循環: 1
主線程循環 : 13,外層循環: 1
主線程循環 : 14,外層循環: 1
主線程循環 : 15,外層循環: 1
主線程循環 : 16,外層循環: 1
主線程循環 : 17,外層循環: 1
主線程循環 : 18,外層循環: 1
主線程循環 : 19,外層循環: 1
主線程循環 : 20,外層循環: 1
主線程循環 : 21,外層循環: 1
主線程循環 : 22,外層循環: 1
主線程循環 : 23,外層循環: 1
主線程循環 : 24,外層循環: 1
主線程循環 : 25,外層循環: 1
主線程循環 : 26,外層循環: 1
主線程循環 : 27,外層循環: 1
主線程循環 : 28,外層循環: 1
主線程循環 : 29,外層循環: 1
主線程循環 : 30,外層循環: 1
主線程循環 : 31,外層循環: 1
主線程循環 : 32,外層循環: 1
主線程循環 : 33,外層循環: 1
主線程循環 : 34,外層循環: 1
主線程循環 : 35,外層循環: 1
主線程循環 : 36,外層循環: 1
主線程循環 : 37,外層循環: 1
主線程循環 : 38,外層循環: 1
主線程循環 : 39,外層循環: 1
主線程循環 : 40,外層循環: 1
主線程循環 : 41,外層循環: 1
主線程循環 : 42,外層循環: 1
主線程循環 : 43,外層循環: 1
主線程循環 : 44,外層循環: 1
主線程循環 : 45,外層循環: 1
主線程循環 : 46,外層循環: 1
主線程循環 : 47,外層循環: 1
主線程循環 : 48,外層循環: 1
主線程循環 : 49,外層循環: 1
主線程循環 : 50,外層循環: 1
主線程循環 : 51,外層循環: 1
主線程循環 : 52,外層循環: 1
主線程循環 : 53,外層循環: 1
主線程循環 : 54,外層循環: 1
主線程循環 : 55,外層循環: 1
主線程循環 : 56,外層循環: 1
主線程循環 : 57,外層循環: 1
主線程循環 : 58,外層循環: 1
主線程循環 : 59,外層循環: 1
主線程循環 : 60,外層循環: 1
主線程循環 : 61,外層循環: 1
主線程循環 : 62,外層循環: 1
主線程循環 : 63,外層循環: 1
主線程循環 : 64,外層循環: 1
主線程循環 : 65,外層循環: 1
主線程循環 : 66,外層循環: 1
主線程循環 : 67,外層循環: 1
主線程循環 : 68,外層循環: 1
主線程循環 : 69,外層循環: 1
主線程循環 : 70,外層循環: 1
主線程循環 : 71,外層循環: 1
主線程循環 : 72,外層循環: 1
主線程循環 : 73,外層循環: 1
主線程循環 : 74,外層循環: 1
主線程循環 : 75,外層循環: 1
主線程循環 : 76,外層循環: 1
主線程循環 : 77,外層循環: 1
主線程循環 : 78,外層循環: 1
主線程循環 : 79,外層循環: 1
主線程循環 : 80,外層循環: 1
主線程循環 : 81,外層循環: 1
主線程循環 : 82,外層循環: 1
主線程循環 : 83,外層循環: 1
主線程循環 : 84,外層循環: 1
主線程循環 : 85,外層循環: 1
主線程循環 : 86,外層循環: 1
主線程循環 : 87,外層循環: 1
主線程循環 : 88,外層循環: 1
主線程循環 : 89,外層循環: 1
主線程循環 : 90,外層循環: 1
主線程循環 : 91,外層循環: 1
主線程循環 : 92,外層循環: 1
主線程循環 : 93,外層循環: 1
主線程循環 : 94,外層循環: 1
主線程循環 : 95,外層循環: 1
主線程循環 : 96,外層循環: 1
主線程循環 : 97,外層循環: 1
主線程循環 : 98,外層循環: 1
主線程循環 : 99,外層循環: 1
主線程循環 : 100,外層循環: 1
子線程循環 : 1,外層循環: 2
子線程循環 : 2,外層循環: 2
子線程循環 : 3,外層循環: 2
子線程循環 : 4,外層循環: 2
子線程循環 : 5,外層循環: 2
子線程循環 : 6,外層循環: 2
子線程循環 : 7,外層循環: 2
子線程循環 : 8,外層循環: 2
子線程循環 : 9,外層循環: 2
子線程循環 : 10,外層循環: 2
主線程循環 : 1,外層循環: 2
主線程循環 : 2,外層循環: 2
主線程循環 : 3,外層循環: 2
主線程循環 : 4,外層循環: 2
主線程循環 : 5,外層循環: 2
主線程循環 : 6,外層循環: 2
主線程循環 : 7,外層循環: 2
主線程循環 : 8,外層循環: 2
主線程循環 : 9,外層循環: 2
主線程循環 : 10,外層循環: 2
主線程循環 : 11,外層循環: 2
主線程循環 : 12,外層循環: 2
主線程循環 : 13,外層循環: 2
主線程循環 : 14,外層循環: 2
主線程循環 : 15,外層循環: 2
主線程循環 : 16,外層循環: 2
主線程循環 : 17,外層循環: 2
主線程循環 : 18,外層循環: 2
主線程循環 : 19,外層循環: 2
主線程循環 : 20,外層循環: 2
主線程循環 : 21,外層循環: 2
主線程循環 : 22,外層循環: 2
主線程循環 : 23,外層循環: 2
主線程循環 : 24,外層循環: 2
主線程循環 : 25,外層循環: 2
主線程循環 : 26,外層循環: 2
主線程循環 : 27,外層循環: 2
主線程循環 : 28,外層循環: 2
主線程循環 : 29,外層循環: 2
主線程循環 : 30,外層循環: 2
主線程循環 : 31,外層循環: 2
主線程循環 : 32,外層循環: 2
主線程循環 : 33,外層循環: 2
主線程循環 : 34,外層循環: 2
主線程循環 : 35,外層循環: 2
主線程循環 : 36,外層循環: 2
主線程循環 : 37,外層循環: 2
主線程循環 : 38,外層循環: 2
主線程循環 : 39,外層循環: 2
主線程循環 : 40,外層循環: 2
主線程循環 : 41,外層循環: 2
主線程循環 : 42,外層循環: 2
主線程循環 : 43,外層循環: 2
主線程循環 : 44,外層循環: 2
主線程循環 : 45,外層循環: 2
主線程循環 : 46,外層循環: 2
主線程循環 : 47,外層循環: 2
主線程循環 : 48,外層循環: 2
主線程循環 : 49,外層循環: 2
主線程循環 : 50,外層循環: 2
主線程循環 : 51,外層循環: 2
主線程循環 : 52,外層循環: 2
主線程循環 : 53,外層循環: 2
主線程循環 : 54,外層循環: 2
主線程循環 : 55,外層循環: 2
主線程循環 : 56,外層循環: 2
主線程循環 : 57,外層循環: 2
主線程循環 : 58,外層循環: 2
主線程循環 : 59,外層循環: 2
主線程循環 : 60,外層循環: 2
主線程循環 : 61,外層循環: 2
主線程循環 : 62,外層循環: 2
主線程循環 : 63,外層循環: 2
主線程循環 : 64,外層循環: 2
主線程循環 : 65,外層循環: 2
主線程循環 : 66,外層循環: 2
主線程循環 : 67,外層循環: 2
主線程循環 : 68,外層循環: 2
主線程循環 : 69,外層循環: 2
主線程循環 : 70,外層循環: 2
主線程循環 : 71,外層循環: 2
主線程循環 : 72,外層循環: 2
主線程循環 : 73,外層循環: 2
主線程循環 : 74,外層循環: 2
主線程循環 : 75,外層循環: 2
主線程循環 : 76,外層循環: 2
主線程循環 : 77,外層循環: 2
主線程循環 : 78,外層循環: 2
主線程循環 : 79,外層循環: 2
主線程循環 : 80,外層循環: 2
主線程循環 : 81,外層循環: 2
主線程循環 : 82,外層循環: 2
主線程循環 : 83,外層循環: 2
主線程循環 : 84,外層循環: 2
主線程循環 : 85,外層循環: 2
主線程循環 : 86,外層循環: 2
主線程循環 : 87,外層循環: 2
主線程循環 : 88,外層循環: 2
主線程循環 : 89,外層循環: 2
主線程循環 : 90,外層循環: 2
主線程循環 : 91,外層循環: 2
主線程循環 : 92,外層循環: 2
主線程循環 : 93,外層循環: 2
主線程循環 : 94,外層循環: 2
主線程循環 : 95,外層循環: 2
主線程循環 : 96,外層循環: 2
主線程循環 : 97,外層循環: 2
主線程循環 : 98,外層循環: 2
主線程循環 : 99,外層循環: 2
主線程循環 : 100,外層循環: 2
子線程循環 : 1,外層循環: 3
子線程循環 : 2,外層循環: 3
子線程循環 : 3,外層循環: 3
子線程循環 : 4,外層循環: 3
子線程循環 : 5,外層循環: 3
子線程循環 : 6,外層循環: 3
子線程循環 : 7,外層循環: 3
子線程循環 : 8,外層循環: 3
子線程循環 : 9,外層循環: 3

註意:

<<Effective Java>>中提到,永遠不要在循環之外調用wait方法。因為,參考:為什麽wait()語句要放在while循環之內

參考資料:

《多線程視頻》張孝祥

JAVA多線程提高二:傳統線程的互斥與同步&傳統線程通信機制