<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>

          Java 開(kāi)發(fā)做了 N 年,精通了 Java 的這幾種鎖!

          共 8760字,需瀏覽 18分鐘

           ·

          2021-09-26 10:15


          小編導(dǎo)讀

          最近在開(kāi)發(fā)項(xiàng)目中遇到了資源同步的問(wèn)題,導(dǎo)致服務(wù)器卡頓,優(yōu)化的時(shí)候使用了鎖的機(jī)制,就突然想寫(xiě)一個(gè)鎖的文章,本篇文章簡(jiǎn)短,但是精髓具在。


          有問(wèn)題和建議的小伙伴可以在最后的留言中說(shuō)出你的問(wèn)題和建議。


          好了,現(xiàn)在直入正題,還是技術(shù)文章老規(guī)矩,先從概念說(shuō)起,了解概念再去實(shí)找,想想都興奮。


          01

          概念


          說(shuō)到Java中的鎖要聯(lián)系到多線程。

          多線程確實(shí)給我們?cè)谛噬辖o我們帶來(lái)了很大的便利,便利的同時(shí)不得不考慮多個(gè)線程之間對(duì)資源競(jìng)爭(zhēng)引起的安全問(wèn)題。

          同步關(guān)鍵字synchronized是我們比較熟悉的用來(lái)解決線程安全的一個(gè)關(guān)鍵字,但是鎖(Lock)是一個(gè)在資源競(jìng)爭(zhēng)激勵(lì)的情況下性能更優(yōu)于synchronized的方法。


          再看一些多并發(fā)文章中,會(huì)提及各種各樣鎖如公平鎖,樂(lè)觀鎖,讀寫(xiě)鎖等等,這篇文章會(huì)說(shuō)一些常用的鎖,把主要的鎖講透,會(huì)用。



          02

          分類


          按照鎖的特性和設(shè)計(jì)來(lái)劃分,分為如下幾類:

          1、公平鎖/非公平鎖

          2、可重入鎖

          3、獨(dú)享鎖/共享鎖

          4、互斥鎖/讀寫(xiě)鎖

          5、樂(lè)觀鎖/悲觀鎖

          6、分段鎖

          7、偏向鎖/輕量級(jí)鎖/重量級(jí)鎖

          8、自旋鎖(java.util.concurrent包下的幾乎都是利用鎖)


          從底層角度看常見(jiàn)的鎖也就兩種:Synchronized和Lock接口以及ReadWriteLock接口(讀寫(xiě)鎖)



          03

          介紹


          我們來(lái)說(shuō)一下每一個(gè)鎖的概念。


          1、公平鎖/非公平鎖

          公平鎖指多個(gè)線程按照申請(qǐng)鎖的順序來(lái)依次獲取鎖。
          非公平鎖指多個(gè)線程獲取鎖的順序并不是按照申請(qǐng)鎖的順序來(lái)獲取,有可能后申請(qǐng)鎖的線程比先申請(qǐng)鎖的線程優(yōu)先獲取到鎖,此極大的可能會(huì)造成線程饑餓現(xiàn)象,遲遲獲取不到鎖。

          由于ReentrantLock是通過(guò)AQS來(lái)實(shí)現(xiàn)線程調(diào)度,可以實(shí)現(xiàn)公平鎖,但是synchroized是非公平的,無(wú)法實(shí)現(xiàn)公平鎖。

          2、可重入鎖

          可重入鎖又名遞歸鎖,是指在同一個(gè)線程,在外層方法獲取鎖的時(shí)候,在進(jìn)入內(nèi)層方法會(huì)自動(dòng)獲取鎖。

          3、獨(dú)享鎖/共享鎖

          獨(dú)享鎖是指該鎖一次只能被一個(gè)線程所持有。

          共享鎖是指該鎖可被多個(gè)線程所持有。

          4、互斥鎖/讀寫(xiě)鎖

          上面講的獨(dú)享鎖/共享鎖就是一種廣義的說(shuō)法,互斥鎖/讀寫(xiě)鎖就是具體的實(shí)現(xiàn)。
          互斥鎖在Java中的具體實(shí)現(xiàn)就是ReentrantLock
          讀寫(xiě)鎖在Java中的具體實(shí)現(xiàn)就是ReadWriteLock

          5、樂(lè)觀鎖/悲觀鎖

          悲觀鎖認(rèn)為對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,一定是會(huì)發(fā)生修改的,哪怕沒(méi)有修改,也會(huì)認(rèn)為修改。因此對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,悲觀鎖采取加鎖的形式。悲觀的認(rèn)為,不加鎖的并發(fā)操作一定會(huì)出問(wèn)題。

          樂(lè)觀鎖則認(rèn)為對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,是不會(huì)發(fā)生修改的。在更新數(shù)據(jù)的時(shí)候,會(huì)采用嘗試更新。樂(lè)觀的認(rèn)為,不加鎖的并發(fā)操作是沒(méi)有事情的。

          6、分段鎖

          分段鎖其實(shí)是一種鎖的設(shè)計(jì),并不是具體的一種鎖,對(duì)于ConcurrentHashMap而言,其并發(fā)的實(shí)現(xiàn)就是通過(guò)分段鎖的形式來(lái)實(shí)現(xiàn)高效的并發(fā)操作。

          7、偏向鎖

          偏向鎖是指一段同步代碼一直被一個(gè)線程所訪問(wèn),那么該線程會(huì)自動(dòng)獲取鎖。降低獲取鎖的代價(jià)。

          8、自旋鎖(java.util.concurrent包下的幾乎都是利用鎖)


          自旋鎖是指嘗試獲取鎖的線程不會(huì)立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點(diǎn)是循環(huán)會(huì)消耗CPU。



          04

          實(shí)戰(zhàn)


          其實(shí)上面說(shuō)的很多概念,讓我沒(méi)有使用前我也是一臉懵的,畢竟使用很少的情況下,只是慣性的去使用,沒(méi)有多想其他,更別說(shuō)有的幾乎沒(méi)有使用過(guò),第一次聽(tīng)說(shuō)的都有。


          遇到這種使用很少或者沒(méi)有使用過(guò)的,最好通過(guò)實(shí)例來(lái)認(rèn)識(shí)。

          我這里不做更多的介紹,只介紹常用的幾種。如果有需要更深入的了解的,加我好友我們暢聊。

          再說(shuō)其他幾種鎖之前我說(shuō)先說(shuō) Lock接口與synchronized關(guān)鍵字。


          1,synchronized關(guān)鍵字

          很多人都指導(dǎo) synchronized關(guān)鍵字 是一種同步鎖。

          它的原理是:一個(gè)線程訪問(wèn)一個(gè)對(duì)象中的synchronized(this)同步代碼塊時(shí),其他試圖訪問(wèn)該對(duì)象的線程將被阻塞。

          import java.util.ArrayList;import java.util.List;
          public class Test { public static void main(String[] args) { System.out.println("使用關(guān)鍵字synchronized"); SyncThread syncThread = new SyncThread(); Thread thread1 = new Thread(syncThread, "SyncThread1"); Thread thread2 = new Thread(syncThread, "SyncThread2"); thread1.start(); thread2.start(); }}class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0; } public void run() { synchronized (this){ for (int i = 0; i < 5; i++) { try {                    System.out.println("線程name:"+Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public int getCount() { return count; }}


          分析:1,當(dāng)兩個(gè)并發(fā)線程(thread1和thread2)訪問(wèn)同一個(gè)對(duì)象(syncThread)中的synchronized代碼塊時(shí),在同一時(shí)刻只能有一個(gè)線程得到執(zhí)行,另一個(gè)線程受阻塞,必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。

          2,Thread1和thread2是互斥的,因?yàn)樵趫?zhí)行synchronized代碼塊時(shí)會(huì)鎖定當(dāng)前的對(duì)象,只有執(zhí)行完該代碼塊才能釋放該對(duì)象鎖,下一個(gè)線程才能執(zhí)行并鎖定該對(duì)象。


          修飾一個(gè)方法

          Synchronized修飾一個(gè)方法很簡(jiǎn)單,就是在方法的前面加synchronized。

          將上面的代碼修改如下:

          public synchronized void run() {   {        for (int i = 0; i < 5; i++) {            try {                System.out.println("線程name:"+Thread.currentThread().getName() + ":" + (count++));                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

          分析:
          1,輸入結(jié)果你會(huì)發(fā)現(xiàn)兩個(gè)線程是交替的
          2,雖然可以使用synchronized來(lái)定義方法,但synchronized并不屬于方法定義的一部分,因此,synchronized關(guān)鍵字不能被繼承。
          如果在父類中的某個(gè)方法使用了synchronized關(guān)鍵字,而在子類中覆蓋了這個(gè)方法,在子類中的這個(gè)方法默認(rèn)情況下并不是同步的,而必須顯式地在子類的這個(gè)方法中加上synchronized關(guān)鍵字才可以


          2,Lock接口


          JDK1.5之后并發(fā)包中新增了Lock接口以及相關(guān)實(shí)現(xiàn)類來(lái)實(shí)現(xiàn)鎖功能。


            Lock lock=new ReentrantLock();  lock.lock();   try{    }finally{    lock.unlock();    }

          分析:上面的代碼時(shí)Lock接口的簡(jiǎn)單使用比較簡(jiǎn)單。

          我們還以一個(gè)售票的機(jī)制來(lái)看 Lock接口鎖時(shí)怎么實(shí)現(xiàn)的?

          import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * 一、用于解決多線程安全問(wèn)題的方式: * 1.同步代碼塊 synchronized 隱式鎖 * 2.同步方法 synchronized 隱式鎖 * 3.同步鎖Lock (jdk1.5以后) 顯示鎖 * 注意:顯示鎖,需要通過(guò)lock()方式上鎖,必須通過(guò)unlock()方式進(jìn)行釋放鎖 */public class TestLock {  public static void main(String[] args) {    TicketCar ticketCar = new TicketCar();    new Thread(ticketCar, "1號(hào)窗口").start();    new Thread(ticketCar, "2號(hào)窗口").start();    new Thread(ticketCar, "3號(hào)窗口").start();  }}class TicketCar implements Runnable {  private int tick = 100;  private Lock lock = new ReentrantLock();  @Override  public void run() {    while (true) {      lock.lock();      try {        if (tick > 0) {          try {            Thread.sleep(200);          } catch (InterruptedException e) {            e.printStackTrace();          }          System.out.println(Thread.currentThread().getName() + " 完成售票,余票為 " + --tick);        }      } finally {        lock.unlock();      }    }  }}


          好了 上面說(shuō)了 我們工作中常用的兩種方式,下面我再簡(jiǎn)單聊幾種比較常用的。

          3,讀寫(xiě)鎖

          關(guān)于使用讀寫(xiě)鎖就是在讀的時(shí)候上讀鎖,寫(xiě)的時(shí)候上寫(xiě)鎖,主要用來(lái)解決synchronized不能解決的問(wèn)題(兩個(gè)線程同時(shí)讀,理論上是可以并行的,但是synchronized是加了鎖的)。

          ReadWriteLock接口中有兩個(gè)方法,分別是readLock和writeLock。源碼如下:

          public interface ReadWriteLock {    /**     * 返回讀鎖     */    Lock readLock();    /**     * 返回寫(xiě)鎖     */    Lock writeLock();}


          關(guān)于讀寫(xiě)鎖
          我們做一個(gè)操作,在寫(xiě)操作的時(shí)候不允許讀操作,讀寫(xiě)分開(kāi)。

          import java.util.HashMap;import java.util.Map;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantReadWriteLock;public class threadTest { static Map<String, Object> map = new HashMap<String, Object>(); static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); static Lock r = rwl.readLock(); static Lock w = rwl.writeLock(); // 獲取一個(gè)key對(duì)應(yīng)的value public static final Object get(String key) { Object object = null; try { r.lock(); System.out.println("正在做讀的操作,key:" + key + " 開(kāi)始"); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } object = map.get(key); System.out.println("正在做讀的操作,key:" + key + " 結(jié)束"); System.out.println(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ r.unlock(); } return object; } // 設(shè)置key對(duì)應(yīng)的value,并返回舊有的value public static final Object put(String key, Object value) { Object object = null; try { w.lock(); System.out.println("正在做寫(xiě)的操作,key:" + key + ",value:" + value + "開(kāi)始."); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } object = map.put(key, value); System.out.println("正在做寫(xiě)的操作,key:" + key + ",value:" + value + "結(jié)束."); System.out.println(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ w.unlock(); } return object; } public static void main(String[] args) { new Thread(new Runnable() { public void run() { for (int i = 0; i < 10; i++) { threadTest.put(i + "", i + ""); } } }).start(); new Thread(new Runnable() { public void run() { for (int i = 0; i < 10; i++) { threadTest.get(i + ""); } } }).start(); }}

          1,調(diào)用讀寫(xiě)方法的時(shí)候 添加了 鎖 lock ,只有在finally執(zhí)行玩調(diào)用unlock,解鎖后才能允許其他鎖的執(zhí)行。


          4,可重入鎖

          對(duì)于Java ReentrantLock而言, 他的名字就可以看出是一個(gè)可重入鎖,其名字是Re entrant Lock重新進(jìn)入鎖。
          對(duì)于Synchronized而言,也是一個(gè)可重入鎖??芍厝腈i的一個(gè)好處是可一定程度避免死鎖。


          5,公平鎖/非公平鎖


          /** * 公平鎖與非公平鎖 */public class FairAndUnFairThread {    public static void main(String[] args) throws InterruptedException {        //默認(rèn)非公平鎖        final Lock lock = new ReentrantLock(true);        final MM m = new MM(lock);        for (int i=1;i<=10 ;i++){            String name = "線程"+i;            Thread tt = new Thread(new Runnable() {                @Override                public void run() {                   for(int i=0;i<2;i++){                       m.testReentrant();                   }                }            },name);            tt.start();        }
          }}class MM { Lock lock = null; MM(Lock lock){ this.lock = lock; }
          public void testReentrant(){ lock.lock(); try{ Thread.sleep(1); System.out.println(Thread.currentThread().getName() ); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }
          public synchronized void testSync(){ System.out.println(Thread.currentThread().getName()); }
          }



          1,這種公平也不是絕對(duì)的 不一定就是按照順序,可能因?yàn)镃PU準(zhǔn)備原因,可能會(huì)有一些不公平的。


          講了這么多鎖,趕緊在開(kāi)發(fā)工具中試試把,多驗(yàn)證幾次什么都明白了。

          本文 Github ( 碼云Gitee同步) https://github.com/ProceduralZC/JavaDevGuide/tree/master/code/JavaBasic  已收錄,歡迎 star。

          —  END — 

          瀏覽 56
          點(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>
                  日本性爱无码 | 五月激情四射网 | 国产精品久久久久久久免费 | 亚洲成a人片77777精品 | 爱操av|