PS:好累啊,好晚纔到家,今天把學的併發編程的最後一點工具和概念總結下,明天正式進入aqs的源碼學習~java
所謂原子操做是指不會被線程調度機制打斷的操做;這種操做一旦開始,就一直運行到結束,中間不會有任何 context switch (線程切換)。數據庫
存在問題:express
一、被阻塞的線程優先級很高編程
二、拿到鎖的線程一直不釋放鎖怎麼辦?數組
三、大量的競爭,消耗cpu,同時帶來死鎖或者其餘安全。安全
Java中的CAS操做正是利用了處理器提供的CMPXCHG指令實現的。自旋CAS實現的基本思路就是循環進行CAS操做直到操做成功爲止。併發
2.一、CAS的原理dom
CAS(Compare And Swap),指令級別保證這是一個原子操做ide
三個運算符: 一個內存地址V,一個指望的值A,一個新值B工具
基本思路:若是地址V上的值和指望的值A相等,就給地址V賦給新值B,若是不是,不作任何操做。循環(死循環,自旋)裏不斷的進行CAS操做
2.二、CAS的問題
一、ABA問題
就是一個縣城可能將A改爲了B,而後又有個線程將B又改爲了A。但此時的A已經不是咱們本來的A了。
就好比喝水,我到了一杯水,而後去上了個廁所,而後同事把我水喝了而後又給我接滿了,等我回來時雖然桌子上仍是一杯水,但此時已經不是個人那杯了。因此爲了解決這個問題,咱們能夠對咱們使用的地址經過加個版本號的概念,來標識咱們的變量是否發生變化。
可以使用AtomicStampedReference和AtomicMarkableReference記錄版本號
二、開銷問題
自旋仍是很消耗性能的
三、只能保證一個共享變量的原子操做
import java.util.concurrent.atomic.AtomicStampedReference; /** * @Auther: BlackKingW * @Date: 2019/4/15 22:09 * @Description:帶版本號的原子操做 */ public class UseAtomicStampedReference { static AtomicStampedReference<String> asr = new AtomicStampedReference<>("BlackKingW",0); public static void main(String[] args) throws InterruptedException { final int oldStamp = asr.getStamp();//那初始的版本號 final String oldReferenc = asr.getReference(); System.out.println(oldReferenc+"==========="+oldStamp); Thread rightStampThread = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() +"當前變量值:"+oldReferenc+"當前版本戳:"+oldStamp+"-" +asr.compareAndSet(oldReferenc, oldReferenc+"Java", oldStamp, oldStamp+1)); } }); Thread errorStampThread = new Thread(new Runnable() { @Override public void run() { String reference = asr.getReference(); System.out.println(Thread.currentThread().getName() +"當前變量值:"+reference+"當前版本戳:"+asr.getStamp()+"-" +asr.compareAndSet(reference, reference+"C", oldStamp, oldStamp+1)); } }); rightStampThread.start(); rightStampThread.join(); errorStampThread.start(); errorStampThread.join(); System.out.println(asr.getReference()+"==========="+asr.getStamp()); } }
lock() 用來獲取鎖。若是鎖已被其餘線程獲取,則進行等待。
unlock() 釋放鎖
Lock接口和synchronized的比較
synchronized:是Java語言內置關鍵字,不須要手動釋放鎖。代碼簡潔,
Lock:是實現的一個類,須要手動釋放鎖。而且獲取鎖能夠被中斷,擁有超時獲取鎖,嘗試獲取鎖等機制。
代碼示例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Auther: BlackKingW * @Date: 2019/4/14 12:09 * @Description: */ public class LockDemo { private Lock lock = new ReentrantLock(); private int count; public void increament() { lock.lock(); try { count++; }finally { lock.unlock(); } } public synchronized void incr2() { count++; incr2(); } }
如increament採用lock,代碼相對複雜,而且使用lock必定要在finally 中釋放鎖,不然可能會永遠都釋放不了,致使死鎖。
可重入意思爲:已經得到該鎖的線程,能夠再次進入被鎖定的代碼塊。內部經過計數器實現。例如上面的代碼
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Auther: BlackKingW * @Date: 2019/4/15 22:09 * @Description: */ public class ReentrantLockDemo { private Lock lock = new ReentrantLock(); private int count; public void increament() { lock.lock(); try { count++; }finally { lock.unlock(); } } public synchronized void incr2() { count++; incr2(); } public synchronized void test3() { incr2(); } }
在增長一個方法test3,去調用incr2,若是該鎖不能夠被重入,則沒法調用incr2。致使程序一直運行不下去。可重入鎖就是支持已經獲取鎖的線程,能夠重複進入加鎖的代碼塊。
公平鎖和非公平鎖。何謂公平性,是針對獲取鎖而言的,若是一個鎖是公平的,那麼鎖的獲取順序就應該符合請求上的絕對時間順序,知足FIFO
當多個線程去請求加鎖代碼塊時,同時只能有一個線程擁有鎖,那麼其餘線程若是是按照到來的前後順序,那麼這個鎖就是公平鎖。好比ReentrantLock可指定是否爲公平和非公平鎖。不然就是非公平鎖。好比synchronized。
公平鎖 VS 非公平鎖
公平鎖每次獲取到鎖爲同步隊列中的第一個節點,保證請求資源時間上的絕對順序,而非公平鎖有可能剛釋放鎖的線程下次繼續獲取該鎖,則有可能致使其餘線程永遠沒法獲取到鎖,形成「飢餓」現象。
公平鎖爲了保證時間上的絕對順序,須要頻繁的上下文切換,而非公平鎖會下降必定的上下文切換,下降性能開銷。所以,ReentrantLock默認選擇的是非公平鎖,則是爲了減小一部分上下文切換,保證了系統更大的吞吐量。
那是否是全部的鎖都只能被一個線程所擁有呢?固然不是。例如ReentrantReadWriteLock讀寫鎖。
ReentrantReadWriteLock容許多個讀線程同時訪問,但不容許寫線程和讀線程、寫線程和寫線程同時訪問。
ReadWriteLock接口有兩個方法
Lock readLock(); 獲取讀鎖
Lock writeLock(); 獲取寫鎖
ReentrantReadWriteLock實現了ReadWriteLock接口。用於獲取讀寫鎖。
ReentrantLock和synchronized關鍵字,同時只能有一個線程持有,因此都是排他鎖,而ReentrantReadWriteLock能夠同時有多個線程去訪問,這種所也叫共享鎖。
使用場景: 讀多寫少的狀況
代碼示例
/** * @Auther: BlackKingW * @Date: 2019/4/15 22:09 * @Description: */ public class UseSyn implements GoodsService { private GoodsInfo goodsInfo; public UseSyn(GoodsInfo goodsInfo) { this.goodsInfo = goodsInfo; } @Override public synchronized GoodsInfo getNum() { SleepTools.ms(5); return this.goodsInfo; } @Override public synchronized void setNum(int number) { SleepTools.ms(5); goodsInfo.changeNumber(number); } } import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @Auther: BlackKingW * @Date: 2019/4/15 22:09 * @Description: */ public class UseRwLock implements GoodsService { private GoodsInfo goodsInfo; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock getLock = lock.readLock();//讀鎖 private final Lock setLock = lock.writeLock();//寫鎖 public UseRwLock(GoodsInfo goodsInfo) { this.goodsInfo = goodsInfo; } @Override public GoodsInfo getNum() { getLock.lock(); try { SleepTools.ms(5); return this.goodsInfo; }finally { getLock.unlock(); } } @Override public void setNum(int number) { setLock.lock(); try { SleepTools.ms(5); goodsInfo.changeNumber(number); }finally { setLock.unlock(); } } } /** * @Auther: BlackKingW * @Date: 2019/4/14 12:09 * @Description: */ public interface GoodsService { public GoodsInfo getNum();//得到商品的信息 public void setNum(int number);//設置商品的數量 } /** * @Auther: BlackKingW * @Date: 2019/4/15 22:09 * @Description: */ public class GoodsInfo { private final String name; private double totalMoney;//總銷售額 private int storeNumber;//庫存數 public GoodsInfo(String name, int totalMoney, int storeNumber) { this.name = name; this.totalMoney = totalMoney; this.storeNumber = storeNumber; } public double getTotalMoney() { return totalMoney; } public int getStoreNumber() { return storeNumber; } public void changeNumber(int sellNumber){ this.totalMoney += sellNumber*25; this.storeNumber -= sellNumber; } } import java.util.Random; import java.util.concurrent.CountDownLatch; /** * @Auther: BlackKingW * @Date:2019/4/15 22:09 * @Description: */ public class BusiApp { static final int readWriteRatio = 10;//讀寫線程的比例 static final int minthreadCount = 3;//最少線程數 //static CountDownLatch latch= new CountDownLatch(1); //讀操做 private static class GetThread implements Runnable{ private GoodsService goodsService; public GetThread(GoodsService goodsService) { this.goodsService = goodsService; } @Override public void run() { // try { // latch.await();//讓讀寫線程同時運行 // } catch (InterruptedException e) { // } long start = System.currentTimeMillis(); for(int i=0;i<100;i++){//操做100次 goodsService.getNum(); } System.out.println(Thread.currentThread().getName()+"讀取商品數據耗時:" +(System.currentTimeMillis()-start)+"ms"); } } //寫操作 private static class SetThread implements Runnable{ private GoodsService goodsService; public SetThread(GoodsService goodsService) { this.goodsService = goodsService; } @Override public void run() { // try { // latch.await();//讓讀寫線程同時運行 // } catch (InterruptedException e) { // } long start = System.currentTimeMillis(); Random r = new Random(); for(int i=0;i<10;i++){//操做10次 SleepTools.ms(50); goodsService.setNum(r.nextInt(10)); } System.out.println(Thread.currentThread().getName() +"寫商品數據耗時:"+(System.currentTimeMillis()-start)+"ms---------"); } } public static void main(String[] args) throws InterruptedException { GoodsInfo goodsInfo = new GoodsInfo("Cup",100000,10000); GoodsService goodsService = new UseRwLock(goodsInfo);/*new UseSyn(goodsInfo);*/ for(int i = 0;i<minthreadCount;i++){ Thread setT = new Thread(new SetThread(goodsService)); for(int j=0;j<readWriteRatio;j++) { Thread getT = new Thread(new GetThread(goodsService)); getT.start(); } SleepTools.ms(100); setT.start(); } //latch.countDown(); } }
經過修改busiApp,使用讀寫鎖,
GoodsService goodsService = new UseRwLock(goodsInfo);
執行完畢時間爲
將busiApp修改成,使用synchronized關鍵字
GoodsService goodsService = new UseSyn(goodsInfo);
執行完畢時間爲
ReentrantReadWriteLock和ReentrantLock支持如下功能:
1)支持公平和非公平的獲取鎖的方式;
2)支持可重入。讀線程在獲取了讀鎖後還能夠獲取讀鎖;寫線程在獲取了寫鎖以後既能夠再次獲取寫鎖又能夠獲取讀鎖;
3)還容許從寫入鎖降級爲讀取鎖,其實現方式是:先獲取寫入鎖,而後獲取讀取鎖,最後釋放寫入鎖。可是,從讀取鎖升級到寫入鎖是不容許的;
4)讀取鎖和寫入鎖都支持鎖獲取期間的中斷;
5)Condition支持。僅寫入鎖提供了一個 Conditon 實現;讀取鎖不支持 Conditon ,readLock().newCondition() 會拋出 UnsupportedOperationException。
在個人併發編程專題三-線程的併發工具類這篇文章裏,講了wait和notify/notifyAll。而Condition接口的功能和wait和notify功能和相似。
Condition主要方法爲
await() 當前線程進入等待狀態
signal() 喚醒一個等待在Condition上的線程
signalAll() 喚醒全部等待在Condition上的線程
await、signal、signalAll和wait、notify、notifyAll的等待通知機制的區別
await、signal、signalAll:創建在lock之上的,使用以前須要綁定lock鎖。準確的通知須要喚醒的對象。喚醒時建議使用signal()方法
wait、notify、notifyAll:創建在Object之上的,使用以前須要獲取對象鎖,不能準確地通知須要喚醒的對象,喚醒時建議使用notifyAll()。
代碼舉例
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Auther: BlackKingW * @Date: 2019/4/14 12:09 * @Description: */ public class ExpressCond { public final static String CITY = "ShangHai"; private int km;/*快遞運輸里程數*/ private String site;/*快遞到達地點*/ private Lock lock = new ReentrantLock(); private Condition keCond = lock.newCondition(); private Condition siteCond = lock.newCondition(); public ExpressCond() { } public ExpressCond(int km, String site) { this.km = km; this.site = site; } /* 變化千米數,而後通知處於wait狀態並須要處理千米數的線程進行業務處理*/ public void changeKm(){ lock.lock(); try { this.km = 101; keCond.signal(); }finally { lock.unlock(); } } /* 變化地點,而後通知處於wait狀態並須要處理地點的線程進行業務處理*/ public void changeSite(){ lock.lock(); try { this.site = "BeiJing"; siteCond.signal(); }finally { lock.unlock(); } } /*當快遞的里程數大於100時更新數據庫*/ public void waitKm(){ lock.lock(); try { while(this.km<=100) { try { keCond.await(); System.out.println("check km thread["+Thread.currentThread().getId() +"] is be notifed."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }finally { lock.unlock(); } System.out.println("the Km is "+this.km+",I will change db"); } /*當快遞到達目的地時通知用戶*/ public void waitSite(){ lock.lock(); try { while(CITY.equals(this.site)) { try { siteCond.await(); System.out.println("check site thread["+Thread.currentThread().getId() +"] is be notifed."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }finally { lock.unlock(); } System.out.println("the site is "+this.site+",I will call user"); } } /** * @Auther: BlackKingW * @Date: 2019/4/14 12:09 * @Description: */ public class TestCond { private static ExpressCond express = new ExpressCond(0,ExpressCond.CITY); /*檢查里程數變化的線程,不知足條件,線程一直等待*/ private static class CheckKm extends Thread{ @Override public void run() { express.waitKm(); } } /*檢查地點變化的線程,不知足條件,線程一直等待*/ private static class CheckSite extends Thread{ @Override public void run() { express.waitSite(); } } public static void main(String[] args) throws InterruptedException { for(int i=0;i<3;i++){ new CheckSite().start(); } for(int i=0;i<3;i++){ new CheckKm().start(); } Thread.sleep(1000); express.changeKm();//快遞里程變化 } }
將上篇文章的例子,進行修改,使用Condition進行通知。能夠發現,當里程數發生變化時,會準確的通知到里程數變化,進行相應的業務處理。而不像執行notify的時候,可能會喚醒等待地點變化的業務。從而致使業務員異常。
本章主要了解幾種顯示鎖。以及重入鎖,排它鎖,共享鎖等鎖的概念。本文的代碼裏leepTools.ms(5);均可使用Thread.Sleep代替。歡迎你們多多指點。