并發(fā)技術12】線程鎖技術的使用

      網(wǎng)友投稿 580 2025-04-04

      線程鎖好比傳統(tǒng)線程模型中的 synchronized 技術,但是比 synchronized 方式更加面向?qū)ο螅c生活中的鎖類似,鎖本身也應該是個對象。兩個線程執(zhí)行的代碼片段如果要實現(xiàn)同步互斥的效果,它們必須用用一個鎖對象。鎖是上在代表要做操的資源的類的內(nèi)部方法中,而不是線程代碼中。這篇文章主要總結(jié)一下線程鎖技術中 Lock鎖、ReadWriteLock 鎖的使用。

      1. Lock的簡單使用

      有了synchronized 的基礎,Lock 就比較簡單了,首先看一個實例:

      public?class?LockTest?{

      public?static?void?main(String[] args)?{

      new?LockTest().init();

      }

      private?void?init()?{

      final Outputer outputer =?new?Outputer();

      // 線程1打印:duoxiancheng

      new?Thread(new?Runnable() {

      @Override

      public?void?run()?{

      while?(true) {

      try?{

      Thread.sleep(5);

      }?catch?(InterruptedException e) {

      // TODO Auto-generated catch block

      e.printStackTrace();

      }

      outputer.output("duoxiancheng");

      }

      }

      }).start();

      ;

      // 線程2打印:eson15

      new?Thread(new?Runnable() {

      @Override

      public?void?run()?{

      while?(true) {

      try?{

      Thread.sleep(5);

      }?catch?(InterruptedException e) {

      // TODO Auto-generated catch block

      e.printStackTrace();

      }

      outputer.output("eson15");

      }

      }

      }).start();

      ;

      }

      // 自定義一個類,保存鎖和待執(zhí)行的任務

      static?class?Outputer?{

      Lock?lock?=?new?ReentrantLock();?//定義一個鎖,Lock是個接口,需實例化一個具體的Lock

      //字符串打印方法,一個個字符的打印

      public?void?output(String name)?{

      int?len = name.length();

      lock.lock();

      try?{

      for?(int?i =?0; i < len; i++) {

      System.out.print(name.charAt(i));

      }

      System.out.println("");

      }?finally?{

      lock.unlock();?//try起來的原因是萬一一個線程進去了然后掛了或者拋異常了,那么這個鎖根本沒有釋放

      }

      }

      }

      這個例子和前面介紹 synchronized 的例子差不多,區(qū)別在于將 synchronized 改成了 lock。從程序中可以看出,使用 Lock 的時候,需要先 new 一個 Lock 對象,然后在線程任務中需要同步的地方上鎖,但是一定要記得放鎖,所以使用 try 塊去處理了一下,將放鎖的動作放在 finally 塊中了。

      這是一個線程任務的情況,如果兩個線程任務也不麻煩,還是在這個類中新建一個任務方法,因為 Lock 是這個類的成員變量,還是可以用這個 lock,而且必須用這個 lock,因為要實現(xiàn)同步互斥,必須使用同一把鎖。

      2. 讀寫鎖的妙用

      鎖又分為讀鎖和寫鎖,讀鎖與讀鎖不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥,這是由 jvm 自己控制的。這很好理解,讀嘛,大家都能讀,不會對數(shù)據(jù)造成修改,只要涉及到寫,那就可能出問題。我們寫代碼的時候只要在掙錢的位置上相應的鎖即可。讀寫鎖有個接口叫 ReadWriteLock,我們可以創(chuàng)建具體的讀寫鎖實例,通過讀寫鎖也可以拿到讀鎖和寫鎖。下面看一下讀寫鎖的例子。

      public?class?ReadWriteLockTest?{

      public?static?void?main(String[] args)?{

      final Queue3 q3 =?new?Queue3();?//封裝共享的數(shù)據(jù)、讀寫鎖和待執(zhí)行的任務的類

      for?(int?i =?0; i

      new?Thread() {?// 開啟三個線程寫數(shù)據(jù)

      public?void?run()?{

      while?(true) {

      q3.put(new?Random().nextInt(10000));

      }

      }

      }.start();

      new?Thread() {?// 開啟三個線程讀數(shù)據(jù)

      public?void?run()?{

      while?(true) {

      q3.get();

      }

      }

      }.start();

      }

      }

      }

      class?Queue3?{

      private?Object data =?null;?// 共享的數(shù)據(jù)

      private?ReadWriteLock rwl =?new?ReentrantReadWriteLock();// 定義讀寫鎖

      // 讀取數(shù)據(jù)的任務方法

      public?void?get()?{

      rwl.readLock().lock();?// 上讀鎖

      try?{

      System.out.println(Thread.currentThread().getName()

      +?":before read: "?+ data);?// 讀之前打印數(shù)據(jù)顯示

      Thread.sleep((long) (Math.random() *?1000));?// 睡一會兒~

      System.out.println(Thread.currentThread().getName()

      +?":after read: "?+ data);?// 讀之后打印數(shù)據(jù)顯示

      }?catch?(InterruptedException e) {

      e.printStackTrace();

      }?finally?{

      rwl.readLock().unlock();// 釋放讀鎖

      }

      }

      // 寫數(shù)據(jù)的任務方法

      public?void?put(Object data)?{

      rwl.writeLock().lock();?// 上寫鎖

      try?{

      System.out.println(Thread.currentThread().getName()

      +?":before write: "?+?this.data);?// 讀之前打印數(shù)據(jù)顯示

      Thread.sleep((long) (Math.random() *?1000));?// 睡一會兒~

      this.data = data;?//寫數(shù)據(jù)

      System.out.println(Thread.currentThread().getName()

      +?":after write: "?+?this.data);?// 讀之后打印數(shù)據(jù)顯示

      }?catch?(InterruptedException e) {

      e.printStackTrace();

      }?finally?{

      rwl.writeLock().unlock();// 釋放寫鎖

      }

      }

      }

      為了說明讀鎖和寫鎖的特點(讀鎖和讀鎖不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥),我先把上面兩個任務方法中上鎖和放鎖的四行代碼注釋掉,來看一下運行結(jié)果。

      其實不管是注釋調(diào)讀鎖還是注釋調(diào)寫鎖,還是全注釋掉,都會出問題,寫的時候會有線程去讀。那么將讀寫鎖加上后,再看一下運行結(jié)果。

      可以看出,有了讀寫鎖,各個線程運行有序,從結(jié)果來看,也印證了讀鎖和讀鎖不互斥,寫鎖與讀鎖、寫鎖都互斥的特點。

      現(xiàn)在使用讀寫鎖寫一個模擬緩存數(shù)據(jù)的 demo,實現(xiàn)功能如下:現(xiàn)在有5個線程都需要拿數(shù)據(jù),一開始是沒有數(shù)據(jù)的,所以最先去拿數(shù)據(jù)的那個線程發(fā)現(xiàn)沒數(shù)據(jù),它就得去初始化一個數(shù)據(jù),然后其他線程拿數(shù)據(jù)的時候就可以直接拿了。代碼如下。

      public?class?ReadWriteLockTest2?{

      public?static?void?main(String[] args)?{

      CacheData cache =?new?CacheData();

      for(int?i =?1; i <=?5; i ++) {?//開啟5個線程

      new?Thread(new?Runnable() {

      @Override

      public?void?run()?{

      cache.processCache();?//都去拿數(shù)據(jù)

      }

      }).start();

      }

      }

      }

      class?CacheData?{

      private?Object data =?null;?// 需要緩存的數(shù)據(jù)

      private?boolean cacheValid;?//用來標記是否有緩存數(shù)據(jù)

      private?ReadWriteLock rwl =?new?ReentrantReadWriteLock();// 定義讀寫鎖

      public?void?processCache()?{

      rwl.readLock().lock();?//上讀鎖

      if(!cacheValid) {?//如果沒有緩存,那說明是第一次訪問,需要給data賦個值

      rwl.readLock().unlock();?//先把讀鎖釋放掉

      rwl.writeLock().lock();?//上寫鎖

      if(!cacheValid) {

      System.out.println(Thread.currentThread().getName() +?": no cache!");

      data =?new?Random().nextInt(1000);?//賦值

      cacheValid =?true;?//標記已經(jīng)有緩存了

      System.out.println(Thread.currentThread().getName() +?": already cached!");

      }

      rwl.readLock().lock();?//再把讀鎖上上

      rwl.writeLock().unlock();?//把剛剛上的寫鎖釋放掉

      }

      System.out.println(Thread.currentThread().getName() +?" get data: "?+ data);

      rwl.readLock().unlock();?//釋放讀鎖

      }

      }

      從代碼中可以看出,在 processCache 方法中對讀鎖和寫鎖的交替使用。一開始進來都是讀數(shù)據(jù)的,所以一開始都是上了讀鎖,但是當?shù)谝粋€線程進來發(fā)現(xiàn)沒有緩存數(shù)據(jù)的時候,它得寫數(shù)據(jù),那么此時它得先把讀鎖給釋放掉,換了把寫鎖,告訴其他線程:“哥們,這里面根本沒數(shù)據(jù)啊,我們被坑了,讓我先弄個數(shù)據(jù)來吧,你們等會兒~”,等該線程初始化好了數(shù)據(jù)后,其他線程就可以讀了,于是它又把讀鎖裝起來了,把寫鎖釋放了,然后它出去了。這就模擬了拿緩存數(shù)據(jù)的一個 demo,可以看出,在一個方法中,同一個線程可以操作兩個鎖的。看一下運行結(jié)果。

      Thread-1: no cache!

      Thread-1: already cached!

      Thread-1 get data: 893

      Thread-0 get data: 893

      Thread-2 get data: 893

      這和 Hibernate 中的那個 load(id, Class.class) 方法有點類似,先拿到的是代理對象,要使用該對象的時候,如果發(fā)現(xiàn)沒有,就新產(chǎn)生一個,如果有了就直接拿來用。

      繼續(xù)進階,如果現(xiàn)在要緩存多個數(shù)據(jù),即要寫一個緩存系統(tǒng),那該如何做呢?一個緩存系統(tǒng)無非就是一個容器,可以存儲很多緩存數(shù)據(jù),很自然的想到使用一個 Map,專門裝緩存數(shù)據(jù),然后供多個線程去使用。所以整個涉及思路,跟上面緩存單個數(shù)據(jù)是一樣的,不過就是多考了一些東西而已,看下代碼。

      public?class?CacheDemo?{

      public?static?void?main(String[] args)?{

      Cache cac =?new?Cache();

      for(int?i =?0; i

      new?Thread(new?Runnable() {

      @Override

      public?void?run()?{

      String?value?= (String) cac.getData("cache1");?//第一個進入的線程要先寫一個數(shù)據(jù)進去(相當于第一次從數(shù)據(jù)庫中取)

      System.out.println(Thread.currentThread().getName() +?": "?+?value);

      }

      }).start();

      }

      for(int?i =?0; i

      new?Thread(new?Runnable() {

      @Override

      public?void?run()?{

      String?value?= (String) cac.getData("cache2");//第一個進入的線程要先寫一個數(shù)據(jù)進去(相當于第一次從數(shù)據(jù)庫中取)

      System.out.println(Thread.currentThread().getName() +?": "?+?value);

      }

      }).start();

      }

      }

      }

      class?Cache?{

      //存儲緩存數(shù)據(jù)的Map,注意HashMap是非線程安全的,也要進行同步操作

      private?Map cache = Collections.synchronizedMap(new?HashMap());

      【并發(fā)技術12】線程鎖技術的使用

      private?ReadWriteLock rwl =?new?ReentrantReadWriteLock();?//定義讀寫鎖

      public?synchronized Object?getData(String key)?{

      rwl.readLock().lock();?//上讀鎖

      Object?value?=?null;

      try?{

      value?= cache.get(key);?//根據(jù)key從緩存中拿數(shù)據(jù)

      if?(value?==?null) {?//如果第一次那該key對應的數(shù)據(jù),拿不到

      rwl.readLock().unlock();?//釋放讀鎖

      rwl.writeLock().lock();?//換成寫鎖

      try?{

      if?(value?==?null) {?//之所以再去判斷,是為了防止幾個線程同時進入了上面那個if,然后一個個都來重寫賦值一遍

      System.out.println(Thread.currentThread().getName() +?" write cache for "?+ key);

      value?=?"aaa"?+ System.currentTimeMillis();?// 實際中是去數(shù)據(jù)庫中取,這里只是模擬

      cache.put(key,?value);?//放到緩存中

      System.out.println(Thread.currentThread().getName() +?" has already written cache!");

      }

      }?finally?{

      rwl.writeLock().unlock();?//寫完了釋放寫鎖

      }

      rwl.readLock().lock();?//換讀鎖

      }

      }?finally?{

      rwl.readLock().unlock();?//最后呢釋放讀鎖

      }

      return?value;?//返回要取的數(shù)據(jù)

      }

      }

      整個代碼的結(jié)構(gòu)和上面的一樣,理解了緩存單個數(shù)據(jù)后,這個代碼也不難理解。這里只是個 demo,實際中可以是跟數(shù)據(jù)庫打交道,第一次從緩存中拿肯定是沒有的,那么就要去數(shù)據(jù)庫中查,然后把取到的數(shù)據(jù)放到緩存中,下次別的線程來就能直接從緩存中取了。看一下運行結(jié)果。

      Thread-0 write cache for cache1

      Thread-0 has already written cache!

      Thread-4 write cache for cache2

      Thread-0: aaa1464782404722

      Thread-4 has already written cache!

      Thread-4: aaa1464782404723

      Thread-3: aaa1464782404723

      Thread-2: aaa1464782404722

      Thread-1: aaa1464782404722

      Thread-5: aaa1464782404723

      從結(jié)果中可以看出,線程 0 首先去緩存中拿 key 為 cache1 的值,沒拿到,往里面寫了一個,然后線程 4 去緩存中拿 key 為 cache2 的值也沒拿到,于是也寫了一個,在此期間線程 0 把值拿了出來,后面幾個線程也隨后陸續(xù)的拿出來了。讀寫鎖的應用還是很廣泛的,而且很好用,就總結(jié)這么多吧。

      任務調(diào)度 緩存

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:怎樣轉(zhuǎn)發(fā)到QQ好友(微信怎么轉(zhuǎn)發(fā)qq好友)
      下一篇:WPS表格如何只能輸入指定的內(nèi)容(wps怎么設置單元格只能輸入指定內(nèi)容)
      相關文章
      国产日韩亚洲大尺度高清| 中文字幕不卡亚洲| 亚洲精品乱码久久久久久自慰 | 亚洲&#228;v永久无码精品天堂久久| 亚洲精品线在线观看| 久久久久亚洲AV片无码| 国产精品亚洲а∨无码播放| 最新国产AV无码专区亚洲| 激情97综合亚洲色婷婷五| 亚洲线精品一区二区三区影音先锋| 亚洲精品无码激情AV| 亚洲综合精品网站| 国产AⅤ无码专区亚洲AV| 狠狠亚洲婷婷综合色香五月排名| 亚洲日韩欧洲乱码AV夜夜摸| 亚洲精品成人无限看| 国产精一品亚洲二区在线播放| 国产成人亚洲综合无码精品| 亚洲av无码一区二区三区网站| 亚洲v高清理论电影| 久久久久久亚洲Av无码精品专口 | 亚洲国产精品网站在线播放| 亚洲中文无码卡通动漫野外 | 亚洲AV无码专区在线播放中文| 久久精品视频亚洲| 91大神亚洲影视在线| 亚洲国产片在线观看| 亚洲午夜精品久久久久久app| 亚洲精品无码久久久久秋霞| 婷婷亚洲综合一区二区| 亚洲爽爽一区二区三区| 亚洲精品无码永久在线观看你懂的| 久久精品国产69国产精品亚洲| 亚洲无删减国产精品一区| 亚洲美女视频免费| 亚洲天堂2017无码中文| 国产偷国产偷亚洲高清人| 一本久久a久久精品亚洲| 亚洲国产成人精品不卡青青草原| 亚洲性一级理论片在线观看| 亚洲人成色777777精品|