大話Android多線程(二) synchronized使用解析

版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載
Github:github.com/AnliaLee
首發(fā)地址:Anlia_掘金
大家要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論

前言

這是Android多線程篇的第二章,在上章我們比較了ThreadRunnable創(chuàng)建線程的異同,也簡單地模擬了多線程執(zhí)行任務(wù)的場景。但實際上,這樣執(zhí)行多線程任務(wù)是不安全的,這章我們將分析為何會出現(xiàn)線程不安全的情況以及如何使用synchronized解決這樣的問題

往期回顧
大話Android多線程(一) Thread和Runnable的聯(lián)系和區(qū)別


synchronized使用解析

同步方法(非靜態(tài))

上回說到小R(Runnable)因為誠信經(jīng)營,生意越來越好了,于是小R便多招了一個業(yè)務(wù)員。某日,在售出10張門票后不久,小R就收到了顧客的投訴,說他們買到了假票。小R懷疑是自己的手下動了手腳,便展開了調(diào)查:

當時的業(yè)務(wù)流程如下

private class SellTask {
    private int ticket = 10;
    public void sellTicket(){
        if (ticket > 0) {
            try{
                Thread.sleep(500);
                Log.e("R公司",Thread.currentThread().getName() + "賣了一張票,編號為r" + (ticket--));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

public class TicketRunnable implements Runnable {
    SellTask sellTask;
    public TicketRunnable(SellTask sellTask){
        this.sellTask = sellTask;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            sellTask.sellTicket();
        }
    }
}

票交由3個業(yè)務(wù)員去賣

SellTask sellTask = new SellTask();
TicketRunnable runnable = new TicketRunnable(sellTask);
Thread r1 = new Thread(runnable, "1號業(yè)務(wù)員");
Thread r2 = new Thread(runnable, "2號業(yè)務(wù)員");
Thread r3 = new Thread(runnable, "3號業(yè)務(wù)員");

r1.start();
r2.start();
r3.start();

調(diào)查發(fā)現(xiàn),出現(xiàn)了多個業(yè)務(wù)員售出編號相同的票的情況

進一步調(diào)查后得知,出現(xiàn)這樣的情況是因為業(yè)務(wù)員答應(yīng)了賣給顧客某編號的票,并收取了訂金,回頭拿票時才發(fā)現(xiàn)哥幾個賣得是同一張票(多個線程先后操作共享數(shù)據(jù)造成數(shù)據(jù)錯誤),沒辦法只能自己復制一張給顧客企圖蒙混過關(guān)。小R不知道該怎么約束自己的手下,遂公開招聘能解決問題的人

這天,一位自稱synchronized的男人前來應(yīng)聘。小R問道:“s先生有何高見啊?”s先生淡定地喝了口茶,答道:

“你現(xiàn)在的業(yè)務(wù)流程不太可靠(線程不安全),讓我來統(tǒng)一管理整個售票業(yè)務(wù),每一張票的出售都需經(jīng)過我的審批,一張票賣完后業(yè)務(wù)員才能來我這再次申請拿票出售,這樣每張票都只能由一個業(yè)務(wù)員進行出售,問題也自然解決了(在Java中每一個對象都有一個內(nèi)部鎖,當使用synchronized關(guān)鍵字聲明某個方法時,該方法將受到對象鎖的保護,這樣一次就只能有一個線程可以進入該方法獲得該對象的鎖,其他線程要想調(diào)用該方法,只能排隊等待。當獲得鎖的線程執(zhí)行完該方法并釋放對象鎖后,別的線程才可拿到鎖進入該方法)?!?/p>

小R聽后覺得這方法不錯,便讓s先生來試試。這次依然是要出售10張票,業(yè)務(wù)流程經(jīng)過s先生改進后如下

private class SellTask {
    private int ticket = 10;
    public synchronized void sellTicket(){//使用synchronized聲明sellTicket方法
        if (ticket > 0) {
            try{
                Thread.sleep(500);
                Log.e("R公司",Thread.currentThread().getName() + "賣了一張票,編號為r" + (ticket--));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

問題果然解決了,小R懸著的心也終于放了下來

![QQ截圖20180130221943.png](http://upload-images.jianshu.io/upload_images/4909537-a3ba6c6a8b9497af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

同一個對象內(nèi)多個同步方法

某日,小R又開始向s先生抱怨起來:“我那幫二愣子手下啊,每次進我辦公室匯報工作都是亂糟糟的,讓他們按順序一個個來就是不聽,s先生覺得該如何管管他們???”s先生依然淡定地抿了口茶,說道:“不急,容我先看看他們是怎么匯報的?!毙便依著s先生的意思安排了兩個手下過來匯報工作

private class ReportTask {
    public void report1(){
        Log.e("R公司","1號業(yè)務(wù)員" + "進辦公室");
        try{
            Log.e("R公司","1號業(yè)務(wù)員" + "開始匯報");
            Thread.sleep(1000);

        }catch (Exception e){
            e.printStackTrace();
        }
        Log.e("R公司","1號業(yè)務(wù)員" + "匯報完畢");
        Log.e("R公司","1號業(yè)務(wù)員" + "出辦公室");
    }

    public void report2(){
        Log.e("R公司","2號業(yè)務(wù)員" + "進辦公室");
        try{
            Log.e("R公司","2號業(yè)務(wù)員" + "開始匯報");
            Thread.sleep(1000);

        }catch (Exception e){
            e.printStackTrace();
        }
        Log.e("R公司","2號業(yè)務(wù)員" + "匯報完畢");
        Log.e("R公司","2號業(yè)務(wù)員" + "出辦公室");
    }
}

public class ReportRunnable1 implements Runnable {
    ReportTask task;
    public ReportRunnable1(ReportTask task){
        this.task = task;
    }

    public void run() {
        task.report1();
    }
}

public class ReportRunnable2 implements Runnable {
    ReportTask task;
    public ReportRunnable2(ReportTask task){
        this.task = task;
    }

    public void run() {
        task.report2();
    }
}

不一會兒,兩個手下前后腳進了辦公室

ReportTask reportTask = new ReportTask();
ReportRunnable1 runnable1 = new ReportRunnable1(reportTask);
ReportRunnable2 runnable2 = new ReportRunnable2(reportTask);

Thread r1 = new Thread(runnable1);
Thread r2 = new Thread(runnable2);
r1.start();
r2.start();

小R揉了揉腦袋,嘆氣道:“唉,他們就是這樣匯報的,每次他們一起講的時候我都不知該聽誰的。”s先生哈哈一笑,道:

“這個不難解決,下次他們再來匯報,進來第一個人我就把門鎖了,讓下一個在門外等,等第一個講完了我再放第二個進來就行了(當一個線程訪問對象的某個synchronized同步方法時,其他線程對對象中所有其它synchronized同步方法的訪問將被阻塞)”

小R聽后深以為然,便又安排剛剛那兩個業(yè)務(wù)員過來重新匯報一次,這次由s先生親自守門(別問我為啥他們傻傻的,計算機就是這么工作的 ╮(╯▽╰)╭)

private class ReportTask {
    public synchronized void report1(){
        Log.e("R公司","1號業(yè)務(wù)員" + "進辦公室");
        try{
            Log.e("R公司","1號業(yè)務(wù)員" + "開始匯報");
            Thread.sleep(1000);

        }catch (Exception e){
            e.printStackTrace();
        }
        Log.e("R公司","1號業(yè)務(wù)員" + "匯報完畢");
        Log.e("R公司","1號業(yè)務(wù)員" + "出辦公室");
    }

    public synchronized void report2(){
        Log.e("R公司","2號業(yè)務(wù)員" + "進辦公室");
        try{
            Log.e("R公司","2號業(yè)務(wù)員" + "開始匯報");
            Thread.sleep(1000);

        }catch (Exception e){
            e.printStackTrace();
        }
        Log.e("R公司","2號業(yè)務(wù)員" + "匯報完畢");
        Log.e("R公司","2號業(yè)務(wù)員" + "出辦公室");
    }
}

不一會兒,兩個業(yè)務(wù)員又來了,這次的結(jié)果令小R非常滿意

看著小R這么開心,s先生不禁潑起了冷水:

“你別高興得太早,你窗戶可沒鎖呢,說不定你那幫二愣子手下進不了門就從窗戶爬進來了(當一個線程訪問對象的某個synchronized同步方法時,另一個線程仍然可以訪問該對象中的非synchronized同步方法)?!?/p>

果然,之后的某次工作匯報中,這樣的事就發(fā)生了

private class ReportTask {
    public synchronized void report1(){
        Log.e("R公司","1號業(yè)務(wù)員" + "進辦公室");
        try{
            Log.e("R公司","1號業(yè)務(wù)員" + "開始匯報");
            Thread.sleep(1000);

        }catch (Exception e){
            e.printStackTrace();
        }
        Log.e("R公司","1號業(yè)務(wù)員" + "匯報完畢");
        Log.e("R公司","1號業(yè)務(wù)員" + "出辦公室");
    }

    public synchronized void report2(){
        Log.e("R公司","2號業(yè)務(wù)員" + "進辦公室");
        try{
            Log.e("R公司","2號業(yè)務(wù)員" + "開始匯報");
            Thread.sleep(1000);

        }catch (Exception e){
            e.printStackTrace();
        }
        Log.e("R公司","2號業(yè)務(wù)員" + "匯報完畢");
        Log.e("R公司","2號業(yè)務(wù)員" + "出辦公室");
    }

    public void report3(){
        Log.e("R公司","3號業(yè)務(wù)員" + "進辦公室");
        try{
            Log.e("R公司","3號業(yè)務(wù)員" + "開始匯報");
            Thread.sleep(1000);

        }catch (Exception e){
            e.printStackTrace();
        }
        Log.e("R公司","3號業(yè)務(wù)員" + "匯報完畢");
        Log.e("R公司","3號業(yè)務(wù)員" + "出辦公室");
    }
}

//線程啟動代碼略...

小R:


同步代碼塊

是日,好友小T前來拜訪小R,卻看見小R的辦公室門窗緊閉,幾個業(yè)務(wù)員在門外排著隊。小T十分疑惑,遂敲門招呼小R讓他開門,然而卻沒有得到任何回應(yīng)。沒辦法,小T只能跟著業(yè)務(wù)員在辦公室外面等了

private class ReportTask {
    public void report1(){
        synchronized(this){
            Log.e("R公司","1號業(yè)務(wù)員" + "進辦公室");
            try{
                Log.e("R公司","1號業(yè)務(wù)員" + "開始匯報");
                Thread.sleep(1000);

            }catch (Exception e){
                e.printStackTrace();
            }
            Log.e("R公司","1號業(yè)務(wù)員" + "匯報完畢");
            Log.e("R公司","1號業(yè)務(wù)員" + "出辦公室");
        }
    }

    public void report2(){
        synchronized(this){
            Log.e("R公司","2號業(yè)務(wù)員" + "進辦公室");
            try{
                Log.e("R公司","2號業(yè)務(wù)員" + "開始匯報");
                Thread.sleep(1000);

            }catch (Exception e){
                e.printStackTrace();
            }
            Log.e("R公司","2號業(yè)務(wù)員" + "匯報完畢");
            Log.e("R公司","2號業(yè)務(wù)員" + "出辦公室");
        }
    }

    public void report3(){
        synchronized(this){
            Log.e("R公司","3號業(yè)務(wù)員" + "進辦公室");
            try{
                Log.e("R公司","3號業(yè)務(wù)員" + "開始匯報");
                Thread.sleep(1000);

            }catch (Exception e){
                e.printStackTrace();
            }
            Log.e("R公司","3號業(yè)務(wù)員" + "匯報完畢");
            Log.e("R公司","3號業(yè)務(wù)員" + "出辦公室");
        }
    }

    public void report4(){
        synchronized (this){
            Log.e("R公司","小T" + "進辦公室");
        }

    }
}
ReportTask reportTask = new ReportTask();
ReportRunnable1 runnable1 = new ReportRunnable1(reportTask);
ReportRunnable2 runnable2 = new ReportRunnable2(reportTask);
ReportRunnable3 runnable3 = new ReportRunnable3(reportTask);
ReportRunnable4 runnable4 = new ReportRunnable4(reportTask);

Thread s1 = new Thread(runnable1);
Thread s2 = new Thread(runnable2);
Thread s3 = new Thread(runnable3);
Thread s4 = new Thread(runnable4);
s1.start();
s2.start();
s3.start();
s4.start();

好不容易等到門開了,一個業(yè)務(wù)員走了出來,小T便一閃身溜了進去,門立刻被小R鎖上了?!澳愀缮赌?,差點就夾到我了!”小T抱怨道。小R不好意思笑笑,說道:“原來是小T啊,你坐你坐,待會再向你解釋,我先放下個業(yè)務(wù)員進來...”

好不容易應(yīng)付完所有業(yè)務(wù)員,小R向小T解釋了來龍去脈,并拿出了一把鑰匙交給小T

private class ReportTask {
    public void report1(){
        synchronized(this){
        //省略部分代碼...
        }
    }

    public void report2(){
        synchronized(this){
        //省略部分代碼...
        }
    }

    public void report3(){
        synchronized(this){
        //省略部分代碼...
        }
    }

    private String window = "window";
    public void report4(){
        synchronized (window){
            Log.e("R公司","小T" + "進辦公室");
        }
    }
}

"s先生自然有應(yīng)對這種情況的妙計,這把鑰匙可以打開窗戶(持有window對象的內(nèi)置鎖),你以后可以從窗戶直接爬進來,不用在門外排隊(synchronized (obj){}同步代碼塊用synchronized聲明方法的作用基本一致,都是對synchronized作用范圍內(nèi)的代碼進行加鎖保護,其區(qū)別在于synchronized同步代碼塊使用更加靈活、輕巧,synchronized (obj){}括號內(nèi)的對象參數(shù)即為該代碼塊持有鎖的對象。例如上述例子中,前面三個report方法中的同步代碼塊持有鎖的對象為ReportTask的實例對象,而report4方法中的同步代碼塊持有鎖的對象則為window。因為對象都有自己的對象鎖,只能保護屬于自己的同步代碼塊或同步方法,所以即使其他線程進入前三個方法的同步代碼塊中并獲得相應(yīng)對象的鎖,也不會阻塞進入report4方法的線程執(zhí)行其中的同步代碼)。"

拿到鑰匙后,小T再也不用和業(yè)務(wù)員一起在門外排隊了


靜態(tài)同步方法

了解了上述知識后,我們回過頭再來理解同步方法和靜態(tài)同步方法的區(qū)別。從持有鎖的對象的不同我們可以將synchronized同步代碼的方式分為兩大派系:

  • synchronized聲明非靜態(tài)方法同步代碼塊的synchronized (this){}和synchronized (非this對象){}這三者持有鎖的對象實例對象(類的實例對象可以有很多個),線程想要執(zhí)行該synchronized作用范圍內(nèi)的同步代碼,需獲得對象鎖
public class SynchronizedTest {
    public synchronized void test1(){
        //持有鎖的對象為SynchronizedTest的實例對象
    }

    public void test2(){
        synchronized (this){
            //持有鎖的對象為SynchronizedTest的實例對象
        }
    }

    private String obj = "obj";
    public void test3(){
        synchronized (obj){
            //持有鎖的對象為obj
        }
    }
}
  • synchronized聲明靜態(tài)方法以及同步代碼塊的synchronized (類.class){}這兩者持有鎖的對象Class對象(每個類只有一個Class對象,而Class對象是Java類編譯后生成的.class文件,它包含了與類有關(guān)的信息),線程想要執(zhí)行該synchronized作用范圍內(nèi)的同步代碼,需獲得類鎖
public class SynchronizedTest {
    public static synchronized void test4(){
        //持有鎖的對象為SynchronizedTest的Class對象(SynchronizedTest.class)
    }

    public void test5(){
        synchronized (SynchronizedTest.class){
            //持有鎖的對象為SynchronizedTest的Class對象(SynchronizedTest.class)
        }
    }
}

有關(guān)實例對象Class對象的詳細知識大家可以繼續(xù)在網(wǎng)上查找資料進行深挖,這里就不贅述了,總之我們要記住的一點是

synchronized同步方法(代碼塊)持有鎖的對象不同,則多線程執(zhí)行相應(yīng)的同步代碼時互不干擾;若相同,則獲得該對象鎖的線程先執(zhí)行同步代碼,其他訪問同步代碼的線程會被阻塞等待鎖的釋放

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

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

  • 本文出自 Eddy Wiki ,轉(zhuǎn)載請注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 2,299評論 0 14
  • Java多線程學習 [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,111評論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,599評論 1 15
  • Hi,歡迎來到朱小峰的文字江山,我是他的第二分身朱小黑,專為踐行得到APP的大神專欄而生。 今天我們繼續(xù)學習劉潤五...
    朱小峰閱讀 292評論 0 2
  • 1、《十七歲單車》 推薦指數(shù):???? 電影彩蛋:兩位女神年少時。 一句話影評:一部單車就是我們整個的青春。 觀后...
    洞見人生百態(tài)閱讀 354評論 0 0

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