多线程基础与JUC进阶笔记
文章目錄
- 1.實現線程的幾種方式
- 1.1.繼承Thread類
- 1.2.實現Runnable接口
- 1.3.實現Callable接口
- 2.Lambda表達式
- 3.靜態代理模式
- 4.synchronized鎖
- 5.死鎖
- 6.Lock鎖
- 7.synchronized與Lock區別
- 8.線程池
- 9.線程狀態
- 10. wait和sleep的區別
- 11.生產者與消費者問題
- 12.什么是JUC
- 13.JUC版的生產者與消費者問題
- 14.8鎖問題
- 15.集合的安全問題
- 15.1.List不安全
- 15.2.Set不安全
- 15.3.map不安全
- 16.Callable
- 17.常用的輔助類
- 17.1.CountDownLatch
- 17.2.CyclicBarrier
- 17.3.Semaphore
- 18.讀寫鎖
- 19.阻塞隊列
- 20.線程池
- 20.1.三大方法
- 20.2.七大參數
- 20.2.1.CPU密集型和IO密集型
- 20.3.四種拒絕策略
- 21.四大函數式接口
- 22.Stream流
- 23.JMM
- 24.Volatile
- 25.CAS
- 26.原子引用
- 27.各種鎖
- 27.1.公平鎖,非公平鎖
- 27.2.可重入鎖
- 27.3.自旋鎖
1.實現線程的幾種方式
1.1.繼承Thread類
- 子類繼承Thread類具備多線程能力
- 啟動線程:子類對象.start()
- 不建議使用:避免OOP單繼承局限性
1.2.實現Runnable接口
- 實現接口Runnable具有多線程能力
- 啟動線程:傳入目標對象+Thread對象.start()
- 推薦使用:避免單繼承局限性,靈活方便,方便同一個對象被多個線程使用
1.3.實現Callable接口
1.實現Callable接口
2.重寫call方法,需要返回值類型
3.創建目標對象
4.創建執行任務:ExecutorService executorService = Executors.newFixedThreadPool(3);
5.提交執行:Future result1 = ser.submit(1)
6.獲取結果:boolean r1= result1.get()
7.關閉服務:ser.shutdownNow()
public class Test3 implements Callable<Boolean> {@Overridepublic Boolean call() throws Exception {for (int i = 0; i <20 ; i++) {System.out.println(Thread.currentThread().getName()+"-----"+i);}return true;}public static void main(String[] args) throws ExecutionException, InterruptedException {Test3 t1 = new Test3();Test3 t2 = new Test3();Test3 t3 = new Test3();//創建執行任務:ExecutorService executorService = Executors.newFixedThreadPool(3);//提交執行Future<Boolean> submit1 = executorService.submit(t1);Future<Boolean> submit2 = executorService.submit(t2);Future<Boolean> submit3 = executorService.submit(t3);// 獲取結果boolean r1= submit1.get();boolean r2= submit2.get();boolean r3= submit3.get();//關閉服務:executorService.shutdownNow();} }2.Lambda表達式
public class Test4 {public static void main(String[] args) {Lambda lambda ;//lambda表達式實現接口lambda = (a,b) -> {System.out.println((a+b));};lambda.print(1,1);} } //函數式接口 interface Lambda{void print(int a,int b); }總結:
- lambda表達式只有一行代碼的情況下才能簡化稱為一行,如果有多行,那么就用代碼塊包裹。
- 使用lambda表達式的前提必須是函數式接口(只有一個方法)
- 多個參數也可以去掉參數類型,要去掉就都去掉,必須加上括號
3.靜態代理模式
- 真實對象和代理對象都要實現同一個接口
- 代理對象要代理真實角色
好處:
- 代理對象可以做很多真實對象做不了的事情
- 真實對象專注做自己的事情
靜態代理模式,婚慶公司舉例:
public class Test5 {public static void main(String[] args) {new WeddingCompany(new You()).happyMarry();}} interface Marry{void happyMarry(); } //角色,你去結婚 class You implements Marry{@Overridepublic void happyMarry() {System.out.println("你在結婚中");} } //代理角色,婚慶公司,幫助你結婚 class WeddingCompany implements Marry{private Marry target;public WeddingCompany(Marry target) {this.target = target;}@Overridepublic void happyMarry() {before();target.happyMarry();after();}private void after() {System.out.println("結婚后收錢");}private void before() {System.out.println("結婚前準備");}}線程的實現原理就是基于靜態代理模式。
new WeddingCompany(new You()).happyMarry();new Thread(()-> System.out.println("我愛你"));Thread也實現了Runnable接口。
Thread就是一個代理對象,代理中間的真實對象Runnable接口。
4.synchronized鎖
synchronized方法:鎖的是當前類的class。
public class Test6 {public static void main(String[] args) {BuyTickets b1= new BuyTickets();new Thread(b1,"張三").start();new Thread(b1,"李四").start();new Thread(b1,"趙五").start();} } class BuyTickets implements Runnable{private int ticketsNum = 10;private boolean flag = true;//外部停止方式@Overridepublic void run() {while (flag){try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}buy();}}private synchronized void buy(){if (ticketsNum<=0){flag=false;return;}System.out.println(Thread.currentThread().getName()+"拿到了"+ticketsNum--);} }synchronized代碼塊:synchronized(鎖的對象){
? 要加鎖的內容
}
private void buy(){synchronized (ticketsNum) {if (ticketsNum <= 0) {flag = false;return;}System.out.println(Thread.currentThread().getName() + "拿到了" + ticketsNum--);}}鎖的對象是進行增刪改的對象。
5.死鎖
當某個同步塊同時擁有“兩個以上對象的鎖時”,就可能會放生死鎖問題
兩個對象互相占著對方的資源,產生僵持。
產生死鎖的四個必要條件
6.Lock鎖
- 從JDK5.0開始, java提供了更強大的線程同步機制——通過顯式定義同步鎖對象來實現同步。同步鎖使用Lock充當。
- java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應先獲得Lock對象
- ReentrantLock類實現了Lock,他擁有與synchronized相同的并發性和內存語義,在實現線程安全的控制中,比較常用的是ReentrantLock可重用鎖,可以顯式加鎖,釋放鎖。
7.synchronized與Lock區別
8.線程池
- JDK5.0起提供了線程池相關API:ExecutorService和Excutors
- ExecutorService:真正的線程池接口。常見子類ThreadPoolExcutor
- void execute(Runnable command) :執行任務/命令,沒有返回值,一般用來執行Runnable
- Future submit(Callable task) :執行任務,有返回值,一般用來執行Callable
- Excutors:工具類,線程池的工廠類,用于創建并返回不同類型的線程池
9.線程狀態
public enum State {/*** 新生*/NEW,/**運行*/RUNNABLE,/**阻塞*/BLOCKED,/*等待*/WAITING,/**超時等待*/TIMED_WAITING,/**死亡*/TERMINATED;}10. wait和sleep的區別
都是線程休眠
1.來自不同的類
wait是Object類中的
sleep是Thread類中的
2.關于鎖的釋放
wait會釋放鎖
sleep不會釋放鎖
3.使用范圍不同
sleep在任何地方都可以使用
wait只有在同步代碼塊中可以使用
4.是否需捕獲異常
wait不需要捕獲異常
sleep需要捕獲異常
11.生產者與消費者問題
/* 生產者消費者問題*/ public class Test7 {public static void main(String[] args) {Data data = new Data();new Thread(()->{for (int i = 0; i <10 ; i++) {try {data.increment();} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decrement();} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decrement();} catch (InterruptedException e) {e.printStackTrace();}}},"C").start();new Thread(()->{for (int i = 0; i <10 ; i++) {try {data.increment();} catch (InterruptedException e) {e.printStackTrace();}}},"D").start();} } class Data{private static Integer num = 0;public synchronized void increment() throws InterruptedException {//注意這里要用while來判斷,不能用if,if只判斷一次會引起虛假喚醒問題while (num!=0){//等待this.wait();}//喚醒其他線程this.notifyAll();num++;System.out.println(Thread.currentThread().getName()+"=>"+num);}public synchronized void decrement() throws InterruptedException {while (num==0){this.wait();}this.notifyAll();num--;System.out.println(Thread.currentThread().getName()+"=>"+num);}}線程也可以喚醒,而不會被通知,中斷或超時,即所謂的虛假喚醒。
等待應該發生在循環中,不應用if判斷。
12.什么是JUC
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-O4X3h0YS-1602919849079)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201015102153149.png)]
juc就是,java.util.cocurrent 包
13.JUC版的生產者與消費者問題
Lock替換synchronized方法和語句的使用, Condition取代了對象監視器方法的使用。 await,signal。
一個Condition實例本質上綁定到一個鎖。 要獲得特定Condition實例的Condition實例,請使用其newCondition()方法。
class Data{private static Integer num = 0;//創建鎖對象ReentrantLock lock = new ReentrantLock();//創建Condition對象,用它其中的方法await 和 signal 來代替wait 和notifyCondition condition = lock.newCondition();public void increment() throws InterruptedException {try{//上鎖lock.lock();//注意這里要用while來判斷,不能用if,if只判斷一次會引起虛假喚醒問題while (num!=0){//等待condition.await();}num++;condition.signal();System.out.println(Thread.currentThread().getName()+"=>"+num);}finally {//釋放鎖lock.unlock();}}public void decrement() throws InterruptedException {try {lock.lock();while (num==0){condition.await();}num--;condition.signal();System.out.println(Thread.currentThread().getName()+"=>"+num);}finally {lock.unlock();}}}對比wait和notify,condition可以精準通知和喚醒線程
/*** 順序通知線程執行,condition精準喚醒線程* ABCD*/ public class Test2 {public static void main(String[] args) {Data3 data3 = new Data3();new Thread(()->{for (int i = 0; i <10 ; i++) {data3.printA();}},"A").start();new Thread(()->{for (int i = 0; i <10 ; i++) {data3.printB();}},"B").start();new Thread(()->{for (int i = 0; i <10 ; i++) {data3.printC();}},"C").start();new Thread(()->{for (int i = 0; i <10 ; i++) {data3.printD();}},"D").start();} } class Data3{private ReentrantLock lock = new ReentrantLock();Condition condition1 = lock.newCondition();Condition condition2 = lock.newCondition();Condition condition3 = lock.newCondition();Condition condition4 = lock.newCondition();private int num = 1;public void printA(){lock.lock();try {while (num!=1){condition1.await();}System.out.println(Thread.currentThread().getName()+"->"+num);num = 2;//喚醒B線程condition2.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void printB(){lock.lock();try {while (num!=2){condition2.await();}System.out.println(Thread.currentThread().getName()+"->"+num);num = 3;//喚醒C線程condition3.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void printC(){lock.lock();try {while (num!=3){condition3.await();}System.out.println(Thread.currentThread().getName()+"->"+num);num = 4;//喚醒Ccondition4.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void printD(){lock.lock();try {while (num!=4){condition4.await();}System.out.println(Thread.currentThread().getName()+"->"+num);num = 1;//喚醒Acondition1.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}} } A->1 B->2 C->3 D->4 A->1 B->2 C->3 D->4 ...14.8鎖問題
關于鎖的8個問題
例子
public class Test3 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendSms();}).start();new Thread(()->{phone.call();}).start();} } class Phone{public synchronized void sendSms(){System.out.println("發短信");}public synchronized void call(){System.out.println("打電話");} }1.標準情況下,兩個線程先打印發短信還是打電話?
先打印發短信,兩個線程共用phone對象同一個鎖,先調用發短信則打電話必須等發短信執行完之后才能執行。
2.sedSms延遲4秒,兩個線程先打印發短信還是打電話?
先打印發短信,和上題情況相同。
3.增加了一個普通方法,先執行發短信還是普通方法?
先執行普通方法,由于普通方法沒有上鎖,所以不用等待發短信執行完畢就可先執行。
4.兩個對象,兩個同步方法,第一個對象執行發短信和打電話,第二個對象執行打電話,先執行發短信還是打電話?
先執行打電話,兩個對象對應了兩個鎖,打電話沒有休眠時間,所以比發短信先執行。
5.將兩個同步方法都改為靜態同步方法,只有一個對象。先執行發短信還是打電話?
先執行發短信, 靜態方法在類初始化的時候就會執行,這時候鎖的就不是對象了,而是Class,按順序執行發短信在前同時擁有鎖,打電話就得等待。
6.5條件中改為兩個對象,先執行發短信還是打電話?
發短信,還是和上題答案一樣,鎖的是Class而不是對象,就和對象無關了。
7.發短信靜態的同步方法,打電話普通的同步方法,先執行發短信還是打電話?
先執行打電話,這時候就有兩個鎖了,發短信占用的是Class的鎖,而打電話占用的是對象的鎖,兩者互不影響
8.上述條件,兩個對象,先執行發短信還是打電話?
還是先執行打電話,和上題解釋相同。
15.集合的安全問題
15.1.List不安全
public class Test4 {public static void main(String[] args) {List<String> list = new ArrayList<>();for (int i = 0; i <10 ; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,5));System.out.println(list);},String.valueOf(i)).start();}} }執行報錯:java.util.ConcurrentModificationException
并發下的ArrayList不安全,看add源碼會發現沒有同步鎖
解決方案:
1**.使用vector集合**
vector集合是線程安全的,底層的add方法使用synchronized加上了同步鎖
但是效率較低
2.使用Collection.synchronizedList(new ArrayList<>())來創建使集合線程安全
3.使用new CopyOnwriteArrayList<>()來創建
在寫入時復制,避免覆蓋造成數據問題。
底層add方法采用lock鎖,更高效
15.2.Set不安全
上面例子換為Set試一試
public class Test5 {public static void main(String[] args) {Set<String> set = new HashSet<String>();for (int i = 0; i <10 ; i++) {new Thread(()->{set.add(UUID.randomUUID().toString().substring(0,5));System.out.println(set);},String.valueOf(i)).start();}} }同樣報java.util.ConcurrentModificationException異常,沒有多試幾次或者增加循環次數。
解決方法:
1.Collections.synchronizedCollection(set)解決
2.new CopyOnWriteArraySet<>()解決。
接著問:HashSet的底層是什么?
HashSet底層就是HashMap
源碼:
public HashSet() {map = new HashMap<>();} public boolean add(E e) {return map.put(e, PRESENT)==null;}private static final Object PRESENT = new Object();//常量HashSet的值不重復利用的就是HashMap的鍵不重復。
15.3.map不安全
public class Test6 {public static void main(String[] args) {Map<String,String> map = new HashMap<>();for (int i = 0; i <30 ; i++) {new Thread(()->{map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));System.out.println(map);},String.valueOf(i)).start();}} }同樣報錯ConcurrentModificationException,
解決:
1.Collections.synchronizedMap(map)
2.使用ConcurrentHashMap
16.Callable
- Callable接口類似于Runnable ,因為它們都是為其實例可能由另一個線程執行的類設計的。然而,A Runnable不返回結果,也不能拋出被檢查的異常。
細節:
1.有緩存
2.結果可能會需要等待,會阻塞。
17.常用的輔助類
17.1.CountDownLatch
減法計數器
public class CountDownLatchTest {public static void main(String[] args) throws InterruptedException {//總數是6CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 1; i <=6 ; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName()+"出去了");countDownLatch.countDown();//數量-1},String.valueOf(i)).start();}//等待總數為0時,再執行下面的任務,即所有人出去后再關門。countDownLatch.await();System.out.println("所有人都出去了,關門");//2出去了//1出去了//3出去了//4出去了//5出去了//6出去了//所有人都出去了,關門} }17.2.CyclicBarrier
加法計數器
public class CyclicBarrierTest {public static void main(String[] args) {//加法計數器,當計數到7時,輸出CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{System.out.println("召喚神龍");});for (int i = 1; i <=7 ; i++) {final int temp = i;new Thread(()->{System.out.println(Thread.currentThread().getName()+"拿到了"+temp+"個龍珠");try {cyclicBarrier.await();//等待到收集完7顆龍珠} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}},String.valueOf(i)).start();}}//1拿到了1個龍珠//2拿到了2個龍珠//3拿到了3個龍珠//4拿到了4個龍珠//5拿到了5個龍珠//6拿到了6個龍珠//7拿到了7個龍珠//召喚神龍 }17.3.Semaphore
信號量
多個線程共享資源,同一時間只允許一定數量的線程占用資源。
模擬搶車位
public class SemaphoreTest {public static void main(String[] args) {//模擬搶車位,共3個車位Semaphore semaphore = new Semaphore(3);//6量車在搶for (int i = 1; i <=6 ; i++) {new Thread(()->{try {semaphore.acquire();System.out.println(Thread.currentThread().getName()+"搶到了車位");TimeUnit.SECONDS.sleep(2);semaphore.release();System.out.println(Thread.currentThread().getName()+"離開了車位");}catch (Exception e){e.printStackTrace();}},String.valueOf(i)).start();}}//1搶到了車位//3搶到了車位//2搶到了車位//1離開了車位//6搶到了車位//2離開了車位//3離開了車位//5搶到了車位//4搶到了車位//6離開了車位//4離開了車位//5離開了車位 }18.讀寫鎖
ReadWriteLock讀寫鎖
讀取鎖(共享鎖):允許多個線程進行讀取操作。
寫入鎖(排他鎖):只允許一個線程進行寫入操作。
public class ReadWriteLockTest {public static void main(String[] args) {MyCache myCache = new MyCache();for (int i = 1; i <=3 ; i++) {final int temp = i;new Thread(()->{myCache.write(temp+"",temp+"");},String.valueOf(i)).start();}for (int i = 1; i <=3 ; i++) {final int temp = i;new Thread(()->{myCache.read(temp+"");},String.valueOf(i)).start();}} } class MyCache{private volatile Map<String,String> map = new HashMap<>();//讀寫鎖private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();//讀取public void read(String key){System.out.println(Thread.currentThread().getName()+"讀取"+key);map.get(key);System.out.println(Thread.currentThread().getName()+"讀取ok");}//寫入public void write(String key,String value){System.out.println(Thread.currentThread().getName()+"寫入"+key);map.put(key,value);System.out.println(Thread.currentThread().getName()+"寫入ok");}//2寫入2//2寫入ok//1寫入1//1寫入ok//3寫入3//3寫入ok//1讀取1//1讀取ok//2讀取2//2讀取ok//3讀取3//3讀取ok }19.阻塞隊列
BlockingQueue阻塞隊列
在多線程并發處理和線程池情況下會使用阻塞隊列
| 添加 | add | offer | put | offer(,) |
| 移除 | remove | poll | take | poll(,) |
| 查看隊首元素 | element | peek | - | - |
同步隊列SynchronousQueue
沒有容量,一個元素進來之后,必須等他出來才能進入下一個元素
public static void main(String[] args) {//沒有容量,一個元素進來之后,必須等他出來才能進入下一個元素SynchronousQueue<String> queue = new SynchronousQueue<>();new Thread(()->{try {System.out.println(Thread.currentThread().getName()+" put 1");queue.put("1");System.out.println(Thread.currentThread().getName()+" put 2");queue.put("2");System.out.println(Thread.currentThread().getName()+" put 3");queue.put("3");} catch (InterruptedException e) {e.printStackTrace();}},"t1").start();new Thread(()->{try {TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName()+" take "+queue.take());TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName()+" take "+queue.take());TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName()+" take "+queue.take());} catch (InterruptedException e) {e.printStackTrace();}},"t2").start();}//t1 put 1//t2 take 1//t1 put 2//t2 take 2//t1 put 3//t2 take 320.線程池
三大方法,7大參數,4種拒絕策略。
線程池的好處
1.降低資源的消耗
2.提高響應的速度
3.方便管理
線程復用,可以控制最大并發數,管理線程
20.1.三大方法
newSingleThreadExecutor()
newFixedThreadPool(3)
newCachedThreadPool()
public static void main(String[] args) {//ExecutorService executorService = Executors.newSingleThreadExecutor();//單個線程池// ExecutorService executorService = Executors.newFixedThreadPool(3);//指定線程池大小ExecutorService executorService = Executors.newCachedThreadPool();//遇強則強,遇弱則弱,最大可達到21億try {for (int i = 0; i <10 ; i++) {executorService.execute(()->{System.out.println(Thread.currentThread().getName()+"執行");});}} finally {executorService.shutdown();}}20.2.七大參數
看一下三大方法的底層源碼
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());} public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}可以看到這三個方法最后都反返回了一個叫ThreadPoolExecutor方法
進去看一看
7大參數就是說的ThreadPoolExecutor中的7大參數
- int corePoolSize,//核心線程數
- int maximumPoolSize,//最大線程數
- long keepAliveTime,//超時等待時間
- TimeUnit unit,//超時單位
- BlockingQueue workQueue,//阻塞隊列
- ThreadFactory threadFactory,//線程工廠,一般不用動
- RejectedExecutionHandler handler//拒絕策略
舉一個銀行排隊的例子來說明創建線程池的七大參數問題。
說明:
正常情況下銀行柜臺只開1號和2號窗口(corePoolSize核心線程數),最多有5個柜臺同事開放(maximumPoolSize最大線程數)
在1號和2號窗口都有人的時候,剩下的人進入等候區(BlockingQueue阻塞隊列),在等候區滿的時候開啟3,4,5號柜臺。
在3,4,5窗口連續1小時沒有人進入的 時候關閉3,4,5號窗口(keepAliveTime超時時間,TimeUnit超時單位)
在5個柜臺全部開放且等候區滿的情況下依然有人進入,這時候選擇拒絕此人,拒絕此人的方法就是RejectedExecutionHandler拒絕策略
在阿里巴巴開發手冊中明確要求不允許使用Excutors創建線程。而是通過ThreadPoolExecutor來創建,參數自己設置。
手動創建一個線程池案例
public static void main(String[] args) {ExecutorService executorService= new ThreadPoolExecutor(2,5,3,TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());try {for (int i = 0; i <5 ; i++) {executorService.execute(()->{System.out.println(Thread.currentThread().getName()+"執行");});}} finally {executorService.shutdown();}}當i的最大值為5時會有2個線程在執行
當i的最大值為6時會有3個線程在執行
當i的最大值為7時會有4個線程在執行
當i的最大值為8時會有5個線程在執行
當i的最大值為9時會拋出異常
所以由此可知線程池最大允許線程執行個數為阻塞隊列大小+最大線程數
20.2.1.CPU密集型和IO密集型
面試題:最大線程到底該如何定義?
1.cpu密集型:cpu是幾核就定義最大線程數是幾。
2.IO密集型:判斷你程序中十分耗IO的線程,假設有10個,那就可以設置2倍于他的最大線程數。
20.3.四種拒絕策略
在上述例子中,使用了默認的拒絕策略AbortPolicy,還有三種拒絕策略
AbortPolicy:
- 阻塞隊列滿了,不處理,并拋出異常。
CallerRunsPolicy:
- 阻塞隊列滿了,哪里來的去哪里,從main線程來的就交個main線程執行
DisCardOldesPolicy:
- 阻塞隊列滿了,丟掉任務不管他并且不會拋出異常
DiscardPolicy:
- 阻塞隊列滿了,嘗試和最早進入線程池的線程競爭,不會拋出異常
21.四大函數式接口
函數式接口:只有一個方法的接口。
Function函數式接口:有一個輸入參數,有一個輸出參數
Function<String,String> function = (str)->{return str;};System.out.println(function.apply("123"));斷定型函數式接口:有一個輸入參數,返回值只能是布爾值
Predicate<String> predicate = (str)->{return false;}; System.out.println(predicate.test("123"));消費型接口:只有輸入參數,沒有返回值
Consumer<String> consumer = (str)->{System.out.println(str);};consumer.accept("123");供給型接口:沒有參數,只有返回值
Supplier<String> supplier = ()->{return "123";};System.out.println(supplier.get());22.Stream流
什么是stream流?
存儲應交給集合和數據庫來做
計算交給流來做
案例
題目要求:
1分鐘內完成此題,只能用1行代碼實現!
現在有5個用戶!篩選:
1、ID 必須是偶數
2、年齡必須大于23歲
3、用戶名轉為大寫字母
4、用戶名字母倒著排序
5、只輸出1個用戶!
public static void main(String[] args) {User u1 = new User(1,"a",21);User u2 = new User(2,"b",26);User u3 = new User(3,"c",24);User u4 = new User(4,"d",25);User u5 = new User(5,"e",26);List<User> list = Arrays.asList(u1,u2,u3,u4,u5);list.stream().filter(user->{return user.getId()%2==0;})//id為偶數.filter(user->{return user.getAge()>23;})//年齡大于23歲.map(user ->{return user.getName().toUpperCase();})//字母轉換為大寫.sorted((user1,user2)->{return user2.compareTo(user1);})//倒序排序.limit(1)//只允許一個輸出.forEach(System.out::println);//遍歷}23.JMM
請你談談對Volatile的理解
Volatile是Java虛擬機提供輕量級的同步機制
1.保證可見性
2.不能保證原子性
3.禁止指令重排
什么是JMM
Java內存模型,不存在的東西,是一種概念,約定。
關于JMM一些同步的約定:
1.線程解鎖前,必須把共享變量立刻刷回主存
2.線程加鎖前,必須讀取主存中的最新值到工作內存中!
3.加鎖和解鎖是同一把鎖
8種操作:
問題:程序不知道主內存里的值已經修改過了
24.Volatile
1.保證可見性
private volatile static int num = 0;public static void main(String[] args) throws InterruptedException {new Thread(()->{while (num==0){}}).start();TimeUnit.SECONDS.sleep(1);num = 1;System.out.println(num);}num加了volatile保證可見性,如果不加volatile線程就監測不到主內存中的num的值已經修改了,就會陷入死循環。
2.不保證原子性
private volatile static int num;public static void add(){num++;}public static void main(String[] args) {for (int i = 0; i <20 ; i++) {new Thread(()->{for (int j = 0; j <1000 ; j++) {add();}}).start();}while (Thread.activeCount()>2){//main,gcThread.yield();//線程禮讓}System.out.println(num);}理論上上述代碼輸出結果應為20000,實際上
這是由于volatile不能保證原子性。
那么如何保證原子性?
在java.util.concurrent.atomic下提供了原子類,可以保證原子性。
改造上述代碼
private volatile static AtomicInteger num=new AtomicInteger();public static void add(){// num++;//不是一個原子性操作num.getAndIncrement();//+1方法}public static void main(String[] args) {for (int i = 0; i <20 ; i++) {new Thread(()->{for (int j = 0; j <1000 ; j++) {add();}}).start();}while (Thread.activeCount()>2){//main,gcThread.yield();//線程禮讓}System.out.println(num);}結果:
3.禁止指令重排
指令重排:程序并不會按照寫的順序來執行,計算機會重排
源代碼–>編譯器優化的重排–>指令并行也可能重排–>內存系統也會重排—>執行
處理器在進行指令重排的時候,考慮:數據之間的依賴性!
| x=a | y=b |
| b=1 | a=2 |
正常的結果:x=0,y=0;但是可能由于指令重排
| b=1 | a=2 |
| x=a | y=b |
結果為:x=2,y=1.
volatile可以避免指令重排:
內存屏障。cpu指令。作用:
1.保證特定的操作的執行順序
2.可以保證某些變量的內存可見性(利用這些特性volatile實現了可見性)
25.CAS
CAS是原子類保證其原子性的原理。
CompareAndSet
源碼:
如果期望的值符合則更新并且返回true,反之返回false。
代碼舉例:
public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(1);//參數1:期望值,參數2:更新的值System.out.println(atomicInteger.compareAndSet(1, 2));System.out.println(atomicInteger);atomicInteger.getAndIncrement();System.out.println(atomicInteger.compareAndSet(1, 2));System.out.println(atomicInteger);}Unsafe類
Java無法操作內存
Java可以通過native調用C++操作內存。
Unsafe是java提供的操作內存的后門。
**CAS:**比較當前工作中內存的值和主內存中的值,如果這個值是期望的,那么就執行操作,如果不是就一直循環。
缺點:
1.循環會耗時。
2.一次性只能保證一個共享變量的原子性。
3.ABA問題。
ABA問題
26.原子引用
解決ABA問題,引入原子引用,對應的思想:樂觀鎖
原子引用在CAS基礎上添加了版本號,記錄對對象的修改操作。
即使內存中的值相同,版本號不同則會返回false
public static void main(String[] args) {//參數1:初始值,參數2:初始版本號AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);new Thread(()->{int stamp = atomicStampedReference.getStamp();//獲得版本號System.out.println("a1=>"+stamp);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//參數:期望值,更新值,期望版本號,更新版本號atomicStampedReference.compareAndSet(1,2,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);System.out.println("a2=>"+atomicStampedReference.getStamp());atomicStampedReference.compareAndSet(2,1,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);System.out.println("a3=>"+atomicStampedReference.getStamp());},"a").start();new Thread(()->{int stamp = atomicStampedReference.getStamp();//獲得版本號System.out.println("b1=>"+stamp);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));System.out.println("b2=>"+atomicStampedReference.getStamp());},"b1").start();}27.各種鎖
27.1.公平鎖,非公平鎖
公平鎖:不允許插隊,先來后到
非公平鎖:允許插隊。
非公平鎖比公平鎖效率更高,例如兩個人上廁所,一個大便一個小便,雖然大便的先來,但是小便的先上廁所效率更高。
synchronized 是非公平鎖
lock默認非公平鎖,但可以手動設置
public ReentrantLock() {sync = new NonfairSync();}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}27.2.可重入鎖
拿到了外面的鎖,就可以拿到里面的鎖,自動獲得。
synchronized
public class Demo01 {public static void main(String[] args) {Phone1 phone1 = new Phone1();new Thread(()->{phone1.sms();}).start();new Thread(()->{phone1.sms();}).start();} } class Phone1 {public synchronized void sms(){System.out.println(Thread.currentThread().getName()+"sms");call();//這里也有一把鎖}public synchronized void call(){System.out.println(Thread.currentThread().getName()+"call");} }lock
class Phone1 {private ReentrantLock lock = new ReentrantLock();public void sms(){lock.lock();System.out.println(Thread.currentThread().getName()+"sms");call();//這里也有一把鎖lock.unlock();}public void call(){lock.lock();System.out.println(Thread.currentThread().getName()+"call");lock.unlock();} }結果一樣。
27.3.自旋鎖
spinLock
當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那么該線程將循環等待,然后不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出循環
自定義一個鎖測試
public class SpinLockDemo {//int 0, Thread nullAtomicReference<Thread> atomicReference = new AtomicReference<>();//加鎖public void myLock(){Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName()+"==》myLock");//自旋鎖,如果獲取不到鎖就一直循環等待while (!atomicReference.compareAndSet(null,thread)){}}public void myUnlock(){Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName()+"==》myUnlock");atomicReference.compareAndSet(thread,null);} } public static void main(String[] args) throws InterruptedException {SpinLockDemo spinLockDemo = new SpinLockDemo();new Thread(()->{spinLockDemo.myLock();try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}spinLockDemo.myUnlock();},"t1").start();TimeUnit.SECONDS.sleep(1);new Thread(()->{spinLockDemo.myLock();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}spinLockDemo.myUnlock();},"t2").start();}t2必須等待t1將鎖釋放后才能執行解鎖。
總結
以上是生活随笔為你收集整理的多线程基础与JUC进阶笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM面试速记
- 下一篇: 关于分页插件PageHelper不起作用