<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          同步鎖-線程安全問題解決方案

          共 9150字,需瀏覽 19分鐘

           ·

          2021-03-26 08:37

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          1 同步鎖

          1.1 前言

          經(jīng)過前面多線程編程的學(xué)習(xí),我們遇到了線程安全的相關(guān)問題,比如多線程售票情景下的超賣/重賣現(xiàn)象.

          我們?nèi)绾闻袛喑绦蛴袥]有可能出現(xiàn)線程安全問題,主要有以下三個(gè)條件:

          在多線程程序中 + 有共享數(shù)據(jù) + 多條語句操作共享數(shù)據(jù)

          多線程的場景和共享數(shù)據(jù)的條件是改變不了的(就像4個(gè)窗口一起賣100張票,這個(gè)是業(yè)務(wù))

          所以思路可以從第3點(diǎn)"多條語句操作共享數(shù)據(jù)"入手,既然是在這多條語句操作數(shù)據(jù)過程中出現(xiàn)了問題

          那我們可以把有可能出現(xiàn)問題的代碼都包裹起來,一次只讓一個(gè)線程來執(zhí)行


          1.2 同步與異步

          那怎么"把有可能出現(xiàn)問題的代碼都包裹起來"呢?我們可以使用synchronized關(guān)鍵字來實(shí)現(xiàn)同步效果

          也就是說,當(dāng)多個(gè)對(duì)象操作共享數(shù)據(jù)時(shí),可以使用同步鎖解決線程安全問題,被鎖住的代碼就是同步的


          接下來介紹下同步與異步的概念:

          同步:體現(xiàn)了排隊(duì)的效果,同一時(shí)刻只能有一個(gè)線程獨(dú)占資源,其他沒有權(quán)利的線程排隊(duì)。

          壞處就是效率會(huì)降低,不過保證了安全。

          異步:體現(xiàn)了多線程搶占資源的效果,線程間互相不等待,互相搶占資源。

          壞處就是有安全隱患,效率要高一些。


          1.3 synchronized同步關(guān)鍵字

          1.3.1 寫法

          synchronized (鎖對(duì)象){

          需要同步的代碼(也就是可能出現(xiàn)問題的操作共享數(shù)據(jù)的多條語句);

          }


          1.3.2 前提

          同步效果的使用有兩個(gè)前提:

          • 前提1:同步需要兩個(gè)或者兩個(gè)以上的線程(單線程無需考慮多線程安全問題)

          • 前提2:多個(gè)線程間必須使用同一個(gè)鎖(我上鎖后其他人也能看到這個(gè)鎖,不然我的鎖鎖不住其他人,就沒有了上鎖的效果)

          1.3.3 特點(diǎn)

          • synchronized同步關(guān)鍵字可以用來修飾方法,稱為同步方法,使用的鎖對(duì)象是this

          • synchronized同步關(guān)鍵字可以用來修飾代碼塊,稱為同步代碼塊,使用的鎖對(duì)象可以任意

          • 同步的缺點(diǎn)是會(huì)降低程序的執(zhí)行效率,但我們?yōu)榱吮WC線程的安全,有些性能是必須要犧牲的

          • 但是為了性能,加鎖的范圍需要控制好,比如我們不需要給整個(gè)商場加鎖,試衣間加鎖就可以了

          為什么同步代碼塊的鎖對(duì)象可以是任意的同一個(gè)對(duì)象,但是同步方法使用的是this呢?

          因?yàn)橥酱a塊可以保證同一個(gè)時(shí)刻只有一個(gè)線程進(jìn)入

          但同步方法不可以保證同一時(shí)刻只能有一個(gè)線程調(diào)用,所以使用本類代指對(duì)象this來確保同步



          1.4 練習(xí)-改造售票案例

          創(chuàng)建包: cn.tedu.thread

          創(chuàng)建類:TestSaleTicketsV2.java

          package cn.tedu.thread;
          /**
           * 本類用于改造售票案例,解決多線程編程安全問題
           * 需求:4個(gè)線程共同賣100張票
           * 問題1:出現(xiàn)了重賣現(xiàn)象:一張票賣給了多個(gè)人
           * 問題2:出現(xiàn)了超賣現(xiàn)象:出現(xiàn)了0張/-1張/-2張這樣的現(xiàn)象
           * */
          public class TestSaleTicketsV2 {
           public static void main(String[] args) {
            //5.創(chuàng)建接口實(shí)現(xiàn)類對(duì)象作為目標(biāo)對(duì)象(目標(biāo)對(duì)象就是要做的業(yè)務(wù))
            SaleTicketsV2 target = new SaleTicketsV2();
            //6.將目標(biāo)對(duì)象與Thread線程對(duì)象綁定
            Thread t1 = new Thread(target);
            Thread t2 = new Thread(target);
            Thread t3 = new Thread(target);
            Thread t4 = new Thread(target);
            //7.以多線程的方式啟動(dòng)線程--會(huì)將線程由新建狀態(tài)變?yōu)榫途w狀態(tài)
            /**1.如果只創(chuàng)建了一個(gè)線程對(duì)象,是單線程場景,不會(huì)出現(xiàn)數(shù)據(jù)問題*/
            t1.start();
            t2.start();
            t3.start();
            t4.start();
           }
          }
          /**2.多線程中出現(xiàn)數(shù)據(jù)不安全問題的前提:多線程程序 + 有共享數(shù)據(jù)(成員變量) +多條語句操作共享數(shù)據(jù)*/
          /**3.同步鎖:相當(dāng)于給容易出現(xiàn)問題的代碼加了一把鎖,包裹了所有可能會(huì)出現(xiàn)問題的代碼
           * 加鎖范圍:不能太大,也不能太小,太大,干啥都得排隊(duì),導(dǎo)致程序的效率太低,太小,沒鎖住,會(huì)有安全問題*/
          //1.自定義售票類,通過實(shí)現(xiàn)Runnable接口的方式完成
          class SaleTicketsV2 implements Runnable{
           //2.定義靜態(tài)成員變量,用來保存票數(shù)
           static int tickets = 100;
           //9.2創(chuàng)建了一個(gè)唯一的"鎖"對(duì)象,不論之后那個(gè)線程進(jìn)同步代碼塊,使用的都是o對(duì)象,"唯一"很重要
           Object o = new Object();
           
           /**6.如果一個(gè)方法里的代碼都被同步了,可以直接把方法修飾成同步方法,同步方法用的鎖對(duì)象是this*/
           //3.添加未實(shí)現(xiàn)方法run()--寫業(yè)務(wù)
           @Override
           //synchronized public void run() {//被synchronized修飾的方法是同步方法
           public void run() {//被synchronized修飾的方法是同步方法
            //4.1通過while(true)死循環(huán)的方式賣票,注意添加出口!!!
            while(true) {
             /**4.synchronized(鎖對(duì)象){}--同步代碼塊:是指同一時(shí)間這一資源會(huì)被一個(gè)線程獨(dú)享,大家使用的時(shí)候,都得排隊(duì),同步效果*/
             /**5.鎖對(duì)象的要求:多線程之間必須使用同一把鎖,同步代碼塊的方式,關(guān)于鎖對(duì)象可以任意定義*/
             //9.出現(xiàn)了線程安全問題,所以需要加同步代碼塊
             //9.1這種寫法不對(duì),相當(dāng)于每個(gè)線程進(jìn)來一次new一個(gè)鎖對(duì)象,并不是使用的同一把鎖
                //synchronized (new Object()) {   
                //9.3 使用同步代碼塊,鎖對(duì)象是o
             synchronized (o) {
              //4.2通過if結(jié)構(gòu)判斷賣票情況
              if(tickets > 0) {//還有票,繼續(xù)售賣
               //8.手動(dòng)添加休眠方法,創(chuàng)造延遲效果,注意可能會(huì)發(fā)生問題,需要由try-catch包裹
               try {
                Thread.sleep(10);//讓線程休眠10ms
               } catch (InterruptedException e) {
                e.printStackTrace();
               }
               //4.3打印當(dāng)前賣票的線程名以及票數(shù)
               System.out.println(Thread.currentThread().getName() + "=" + tickets--);
              }
              //4.4 沒票了,退出循環(huán),沒的賣了
              if(tickets <= 0) break;
             }
            }
           }



          1.5 之前遇到過的同步例子

          StringBuffer JDK1.0

          加了synchronized ,性能相對(duì)較低(要排隊(duì),同步),安全性高

          StringBuilder JDK1.5

          去掉了synchronized,性能更高(不排隊(duì),異步),存在安全隱患


          快速查找某個(gè)類的快捷鍵:Ctrl+Shift+T


          2 線程創(chuàng)建的其他方式

          2.1 ExecutorService/Executors

          ExecutorService:用來存儲(chǔ)線程的池子,把新建線程/啟動(dòng)線程/關(guān)閉線程的任務(wù)都交給池來管理

          • execute(Runnable任務(wù)對(duì)象) 把任務(wù)丟到線程池


          Executors 輔助創(chuàng)建線程池的工具類

          • newFixedThreadPool(int nThreads) 最多n個(gè)線程的線程池

          • newCachedThreadPool() 足夠多的線程,使任務(wù)不必等待

          • newSingleThreadExecutor() 只有一個(gè)線程的線程池

          2.2 練習(xí):線程的其他創(chuàng)建方式

          創(chuàng)建包: cn.tedu.thread

          創(chuàng)建類: TestSaleTicketsV2.java

          public class TestSaleTicketsV2 {
           public static void main(String[] args) {
            //5.創(chuàng)建接口實(shí)現(xiàn)類對(duì)象作為目標(biāo)對(duì)象(目標(biāo)對(duì)象就是要做的業(yè)務(wù))
            SaleTicketsV2 target = new SaleTicketsV2();
            //6.將目標(biāo)對(duì)象與Thread線程對(duì)象綁定
          //  Thread t1 = new Thread(target);
          //  Thread t2 = new Thread(target);
          //  Thread t3 = new Thread(target);
          //  Thread t4 = new Thread(target);
            //7.以多線程的方式啟動(dòng)線程--會(huì)將線程由新建狀態(tài)變?yōu)榫途w狀態(tài)
            /**1.如果只創(chuàng)建了一個(gè)線程對(duì)象,是單線程場景,不會(huì)出現(xiàn)數(shù)據(jù)問題*/
          //  t1.start();
          //  t2.start();
          //  t3.start();
          //  t4.start();
            /**7.線程池ExecutorService:用來存儲(chǔ)線程的池子,把新建線程/啟動(dòng)線程/關(guān)閉線程的任務(wù)都交給池來管理*/
            /**8.Executors用來輔助創(chuàng)建線程池對(duì)象,newFixedThreadPool()創(chuàng)建具有參數(shù)個(gè)數(shù)的線程數(shù)的線程池*/
            ExecutorService pool = Executors.newFixedThreadPool(5);
            for(int i = 0;i<5;i++) {
             /**9.excute()讓線程池中的線程來執(zhí)行任務(wù),每次調(diào)用都會(huì)啟動(dòng)一個(gè)線程*/
             pool.execute(target);//本方法的參數(shù)就是執(zhí)行的業(yè)務(wù),也就是實(shí)現(xiàn)類的目標(biāo)對(duì)象
            }
           }


          3 拓展:線程鎖

          3.1 悲觀鎖和樂觀鎖

          悲觀鎖:像它的名字一樣,對(duì)于并發(fā)間操作產(chǎn)生的線程安全問題持悲觀狀態(tài).

          悲觀鎖認(rèn)為競爭總是會(huì)發(fā)生,因此每次對(duì)某資源進(jìn)行操作時(shí),都會(huì)持有一個(gè)獨(dú)占的鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了。


          樂觀鎖:還是像它的名字一樣,對(duì)于并發(fā)間操作產(chǎn)生的線程安全問題持樂觀狀態(tài).

          樂觀鎖認(rèn)為競爭不總是會(huì)發(fā)生,因此它不需要持有鎖,將”比較-替換”這兩個(gè)動(dòng)作作為一個(gè)原子操作嘗試去修改內(nèi)存中的變量,如果失敗則表示發(fā)生沖突,那么就應(yīng)該有相應(yīng)的重試邏輯。


          3.2 兩種常見的鎖

          synchronized 互斥鎖(悲觀鎖,有罪假設(shè))

          采用synchronized修飾符實(shí)現(xiàn)的同步機(jī)制叫做互斥鎖機(jī)制,它所獲得的鎖叫做互斥鎖。

          每個(gè)對(duì)象都有一個(gè)monitor(鎖標(biāo)記),當(dāng)線程擁有這個(gè)鎖標(biāo)記時(shí)才能訪問這個(gè)資源,沒有鎖標(biāo)記便進(jìn)入鎖池。任何一個(gè)對(duì)象系統(tǒng)都會(huì)為其創(chuàng)建一個(gè)互斥鎖,這個(gè)鎖是為了分配給線程的,防止打斷原子操作。每個(gè)對(duì)象的鎖只能分配給一個(gè)線程,因此叫做互斥鎖。


          ReentrantLock 排他鎖(悲觀鎖,有罪假設(shè))

          ReentrantLock是排他鎖,排他鎖在同一時(shí)刻僅有一個(gè)線程可以進(jìn)行訪問,實(shí)際上獨(dú)占鎖是一種相對(duì)比較保守的鎖策略,在這種情況下任何“讀/讀”、“讀/寫”、“寫/寫”操作都不能同時(shí)發(fā)生,這在一定程度上降低了吞吐量。然而讀操作之間不存在數(shù)據(jù)競爭問題,如果”讀/讀”操作能夠以共享鎖的方式進(jìn)行,那會(huì)進(jìn)一步提升性能。


          ReentrantReadWriteLock 讀寫鎖(樂觀鎖,無罪假設(shè))

          因此引入了ReentrantReadWriteLock,顧名思義,ReentrantReadWriteLock是Reentrant(可重入)Read(讀)Write(寫)Lock(鎖),我們下面稱它為讀寫鎖。

          讀寫鎖內(nèi)部又分為讀鎖和寫鎖,讀鎖可以在沒有寫鎖的時(shí)候被多個(gè)線程同時(shí)持有,寫鎖是獨(dú)占的。

          讀鎖和寫鎖分離從而提升程序性能,讀寫鎖主要應(yīng)用于讀多寫少的場景。


          3.3 嘗試用讀寫鎖改造售票案例

          package cn.tedu.thread;

          import java.util.concurrent.ExecutorService;
          import java.util.concurrent.Executors;
          import java.util.concurrent.locks.ReentrantReadWriteLock;

          /**
           * 本類用于改造售票案例,使用可重入讀寫鎖
           * ReentrantReadWriteLock
           * */
          public class TestSaleTicketsV3 {
           public static void main(String[] args) {
            SaleTicketsV3 target = new SaleTicketsV3();
            Thread t1 = new Thread(target);
            Thread t2 = new Thread(target);
            Thread t3 = new Thread(target);
            Thread t4 = new Thread(target);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
           }
          }
          class SaleTicketsV3 implements Runnable{
           static int tickets = 100;
           //1.定義可重入讀寫鎖對(duì)象,靜態(tài)保證全局唯一
           static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
           @Override
           public void run() {
            while(true) {
             //2.在操作共享資源前上鎖
             lock.writeLock().lock();
             try {
              if(tickets > 0) {
               try {
                Thread.sleep(10);
               } catch (InterruptedException e) {
                e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName() + "=" + tickets--);
              }
              if(tickets <= 0) break;
             } catch (Exception e) {
              e.printStackTrace();
             }finally {
              //3.finally{}中釋放鎖,注意一定要手動(dòng)釋放,防止死鎖,否則就獨(dú)占報(bào)錯(cuò)了
              lock.writeLock().unlock();
             }
            }
           }



          3.4 兩種方式的區(qū)別

          需要注意的是,用sychronized修飾的方法或者語句塊在代碼執(zhí)行完之后鎖會(huì)自動(dòng)釋放,而是用Lock需要我們手動(dòng)釋放鎖,所以為了保證鎖最終被釋放(發(fā)生異常情況),要把互斥區(qū)放在try內(nèi),釋放鎖放在finally內(nèi)!

          與互斥鎖相比,讀-寫鎖允許對(duì)共享數(shù)據(jù)進(jìn)行更高級(jí)別的并發(fā)訪問。雖然一次只有一個(gè)線程(writer 線程)可以修改共享數(shù)據(jù),但在許多情況下,任何數(shù)量的線程可以同時(shí)讀取共享數(shù)據(jù)(reader 線程)從理論上講,與互斥鎖定相比,使用讀-寫鎖允許的并發(fā)性增強(qiáng)將帶來更大的性能提高。


          恭喜你,線程與線程鎖的學(xué)習(xí)可以暫時(shí)告一段落啦,接著我們可以繼續(xù)學(xué)習(xí)別的內(nèi)容

          ————————————————

          版權(quán)聲明:本文為CSDN博主「程序媛 泡泡」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。

          原文鏈接:

          https://blog.csdn.net/weixin_43884234/article/details/115049704





          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長按上方微信二維碼 2 秒


          感謝點(diǎn)贊支持下哈 

          瀏覽 76
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91av-91av在线盒子 | 美国中文字幕在线 | 豆花视频一区二区三区在线观看 | 美女自拍视频 | 青青草小视频 |