線程的同步互斥

  • 當(dāng)多個(gè)線程對(duì)同一個(gè)數(shù)據(jù)進(jìn)行操作的時(shí)候,就會(huì)出現(xiàn)線程安全問(wèn)題。
  • 比如銀行轉(zhuǎn)賬問(wèn)題:同一個(gè)賬戶一邊進(jìn)行出賬操作(淘寶支付),另一邊進(jìn)行入賬操作(別人給自己匯款),此時(shí)會(huì)因?yàn)榫€程同步帶來(lái)安全性問(wèn)題。
  • 以下舉一個(gè)線程安全問(wèn)題的實(shí)例:
    兩個(gè)線程不停地向屏幕輸出字符串,A線程輸出feifeilover,B線程輸出xiaoxin,
    所要達(dá)到的目的是:屏幕顯示完整的字符串。

代碼如下:

package com.java;
public class Threadtrodition00 {
    public static void main(String[] args) {
        new Threadtrodition00().init(); 
    }   
    private void init() {
        final Outputer output = new Outputer();
        new Thread(new Runnable() {  //線程運(yùn)行的代碼在Runnable對(duì)象里面
            
            @Override
            public void run() {   //run中while循環(huán)是為了不停地運(yùn)行
                while(true) {
                    try {
                        Thread.sleep(10);   
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    output.output("feifeilover");
                }
            }
        }).start();
        new Thread(new Runnable() {  //線程運(yùn)行的代碼在Runnable對(duì)象里面
            
            @Override
            public void run() {   //run中while循環(huán)是為了不停地運(yùn)行
                while(true) {
                    try {
                        Thread.sleep(10);   
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    output.output("xiaoxin");
                }
            }
        }).start();//main中啟動(dòng)兩個(gè)線程
    }
        
    class Outputer { // 定義一個(gè)內(nèi)部類,此類為一個(gè)輸出器
        public void output(String string) { // 這個(gè)方法是為了把字符串的內(nèi)容打印到屏幕上
            int len = string.length();
            for (int i = 0; i < len; i++) {
                System.out.print(string.charAt(i));// 把字符一個(gè)一個(gè)的打印到屏幕
            }
            System.out.println(""); // 換行
        }
    }
}

注:內(nèi)部類不能訪問(wèn)局部變量,為訪問(wèn)局部變量要加final;
靜態(tài)方法里面不能new內(nèi)部類的實(shí)例對(duì)象

  • 執(zhí)行后的代碼如下顯示


    這里寫圖片描述

理想狀態(tài)下我們希望上一個(gè)字符串打完以后,在執(zhí)行別的,從執(zhí)行后的結(jié)果顯示,它沒(méi)有等一個(gè)字符串全部輸出,cpu卻跑去執(zhí)行另一個(gè)線程了;
這就是因?yàn)榫€程不同步,而使兩個(gè)線程都在使用同一個(gè)對(duì)象。

  • 這里先給出一個(gè)聲明:

同步(Synchronous) 同步方法調(diào)用一旦開(kāi)始,調(diào)用者必須等到方法調(diào)用返回后,才能繼續(xù)后繼的行為。

  • 要從根本上解決上述問(wèn)題 ,就必須保證兩個(gè)線程A、B在對(duì)i輸出操作時(shí)完全同步。即在線程A寫入時(shí),線程B不僅不能寫,同時(shí)也不能讀。因?yàn)樵诰€程A寫完之前,線程B讀取的一定是一個(gè)過(guò)期數(shù)據(jù)

  • java中,提供了一個(gè)重要的關(guān)鍵字synchronized來(lái)實(shí)現(xiàn)這個(gè)功能。它的功能是對(duì)同步的代碼加鎖,使得每一次,只能有一個(gè)線程進(jìn)入同步塊,從而保證線程間的安全性(即上面代碼的for語(yǔ)句每次應(yīng)該只有一個(gè)線程可以執(zhí)行)。

Synchrouized關(guān)鍵字常用的幾種方法:

1.指定加鎖對(duì)象:對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼前要獲得給定對(duì)象的鎖。

class Outputer {
        String str = "";    
        public void output(String string) {
            int len = string.length();
            synchronized (str) {   //加鎖并傳入同一個(gè)對(duì)象
                for(int i=0;i<len;i++) {
                    System.out.print(string.charAt(i));
                }
                System.out.println("");
            }
        }
    }//內(nèi)部類,是一個(gè)輸出器 

用Synchronized實(shí)現(xiàn)同步互斥,在鎖中一定要是同一個(gè)對(duì)象。

前面我們提到的A線程是output對(duì)象,B線程是output對(duì)象。這兩個(gè)使用的是同一個(gè)對(duì)象,只需在內(nèi)部類中加入String xxx = “”;獲得Outputer的鎖。
由以上代碼可以看出鎖就是Outputer里面的str。Outputer對(duì)象在外部看是output,而在內(nèi)部看就是this。所以代碼可以簡(jiǎn)化為:

class Outputer {
        public void output(String string) {
            int len = string.length();
            synchronized (this) {  
                for(int i=0;i<len;i++) {
                    System.out.print(string.charAt(i));
                }
                System.out.println("");
            }
        }
    }//內(nèi)部類,是一個(gè)輸出器 

2 . 直接作用于實(shí)例對(duì)象:相當(dāng)于對(duì)當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖
方法返回值前加synchronized(一般一段代碼中只用一次synchronized,為了防止死鎖)

class Outputer {
        public synchronized void output(String string) {
            int len = string.length();
                for(int i=0;i<len;i++) {
                    System.out.print(string.charAt(i));
                }
                System.out.println("");
            }
    }//內(nèi)部類,是一個(gè)輸出器 

3.直接作用于靜態(tài)方法:相當(dāng)于對(duì)當(dāng)前類加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類的鎖。
靜態(tài)同步方法使用的鎖是該方法所在的class文件對(duì)象
代碼如下:

static class Outputer {
        public synchronized void output(String string) {
            int len = string.length();
                for(int i=0;i<len;i++) {
                    System.out.print(string.charAt(i));
                }
                System.out.println("");
            }
    }//內(nèi)部類,是一個(gè)輸出器 
    
    public static synchronized void output3(String string) {
        int len = string.length();

        for (int i = 0; i < len; i++) {
            System.out.print(string.charAt(i));
        }
        System.out.println("");

    }

注:關(guān)鍵字synchronized可以修飾方法或者以同步塊的形式來(lái)進(jìn)行使用,它主要確保多個(gè)線程在同一個(gè)時(shí)刻,只能有一個(gè)線程處于方法或者同步塊中,它確保了線程對(duì)變量訪問(wèn)的可見(jiàn)性和排他性。


經(jīng)典面試題:

  • 子線程循環(huán)10次,接著主線程循環(huán)100次,接著又回到子線程循環(huán)10次,接著再回到主線程又循環(huán)100次,如此循環(huán)50次。

首先,將子線程和主線程中要同步的方法進(jìn)行封裝,加上同步關(guān)鍵字實(shí)現(xiàn)同步。
代碼如下:

package com.java;

public class TraditionalThread {
    public static void main(String[] args) {
        final Business business = new Business();   //創(chuàng)建一個(gè)business對(duì)象
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                for(int i=1;i<=50;i++) {    //來(lái)回循環(huán)50次
                    business.sub(i);
                }
            }
        }).start();
        for(int i=1;i<=50;i++) {
            business.main(i);
        }
    }
}   //先起兩個(gè)線程,主線程和子線程

class Business {    //定義內(nèi)部類
    public synchronized void sub(int i) {    //定義子線程  (加鎖實(shí)現(xiàn)同步)
         for(int j=1;j<=10;j++) {
             System.out.println("sub "+j +","+"loop of " +i);
         } 
    }
    
    public synchronized void main(int i) {   //定義主線程(加鎖實(shí)現(xiàn)同步)
        for(int j=1;j<=100;j++) {
            System.out.println("main " + j+","+"loop of " +i);
        }
    }
}   

以上代碼實(shí)現(xiàn)了兩個(gè)線程的互斥。

等待/通知機(jī)制

  • 一個(gè)線程A調(diào)用了對(duì)象O的wait()方法進(jìn)入等待狀態(tài),而另一個(gè)線程B()調(diào)用了對(duì)象O的notify()方法,線程A收到通知后從對(duì)象O的wait()方法返回,進(jìn)而執(zhí)行后續(xù)操作。以上兩個(gè)線程就是通過(guò)對(duì)象O來(lái)完成交互的。

wait與notify實(shí)現(xiàn)線程間的通信代碼(以上述面試題為例)

class Business { // 定義內(nèi)部類
    private boolean BShould = true;

    public synchronized void sub(int i) { // 定義子線程 (加鎖實(shí)現(xiàn)同步)
        if (!BShould) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 10; j++) {
            System.out.println("sub " + j + "," + "loop of " + i);
        }
        BShould = false;
        this.notify();
    }

    public synchronized void main(int i) { // 定義主線程(加鎖實(shí)現(xiàn)同步)
        if (BShould) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 100; j++) {
            System.out.println("main " + j + "," + "loop of " + i);
        }
        BShould = true;
        this.notify();
    }
}
  1. 在使用wait、notify方法時(shí)需要先對(duì)調(diào)用對(duì)象加鎖
  2. notify方法調(diào)用后,等待線程依舊不會(huì)從wait()返回,需要調(diào)用notify()的線程釋放鎖之后, 等待線程才能有機(jī)會(huì)從wait()返回。
  3. wait()返回的前提是獲得了調(diào)用對(duì)象的鎖。

注:此系列博客參照張孝祥的java并發(fā)視頻,以及java高并發(fā)程序等書寫的,因?yàn)楸救诵“渍谂W(xué)習(xí)中,對(duì)知識(shí)掌握的特別的膚淺,如果看到我的博文,有什么不對(duì)的地方,或者是對(duì)我文章有意見(jiàn)的,可以私信給我,我會(huì)一直不斷改進(jìn)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容