日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

多线程基础与JUC进阶笔记

發布時間:2025/3/20 编程问答 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 多线程基础与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單繼承局限性
public class Test1 extends Thread{@Overridepublic void run() {for (int i = 0; i <20 ; i++) {System.out.println("線程"+i);}}public static void main(String[] args) {Test1 test1 =new Test1();test1.start();for (int i = 0; i <20 ; i++) {System.out.println("主線程"+i);}} }

1.2.實現Runnable接口

  • 實現接口Runnable具有多線程能力
  • 啟動線程:傳入目標對象+Thread對象.start()
  • 推薦使用:避免單繼承局限性,靈活方便,方便同一個對象被多個線程使用
public class Test2 implements Runnable{@Overridepublic void run() {for (int i = 0; i <20 ; i++) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"---"+i);}}public static void main(String[] args) {Test2 test2 = new Test2();new Thread(test2,"線程1").start();new Thread(test2,"線程2").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可重用鎖,可以顯式加鎖,釋放鎖。
    //new一個鎖對象ReentrantLock lock = new ReentrantLock();private void buy(){//加鎖try {lock.lock();if (ticketsNum <= 0) {flag = false;return;}System.out.println(Thread.currentThread().getName() + "拿到了" + ticketsNum--);}finally {//釋放鎖lock.unlock();}}

    7.synchronized與Lock區別

  • synchronized 內置的java關鍵字,lock是一個java類
  • synchronized無法判斷獲取鎖的狀態,lock可以判斷是否獲得了鎖
  • synchronized會自動釋放鎖,lock必須手動釋放鎖,如果不釋放就會死鎖
  • synchronized造成的阻塞線程會一直等待下去,而lock不一定會等
  • synchronized可重入鎖,不可以中斷,非公平,lock可重入鎖,可以判斷鎖,默認非公平(可以自己設置)
  • 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不返回結果,也不能拋出被檢查的異常。
    public class CallableTest {public static void main(String[] args) {MyThread myThread = new MyThread();//FutureTask實現了RunnableFuture接口,RunnableFuture繼承了RunnableFutureTask futureTask = new FutureTask<>(myThread);//適配類new Thread(futureTask).start();//結果會被緩存,效率高try {String o = (String) futureTask.get();//可能會產生阻塞,把他放到最后System.out.println(o);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}} } class MyThread implements Callable<String>{@Overridepublic String call() throws Exception {System.out.println("call()");return "1024";} }

    細節:

    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阻塞隊列

    在多線程并發處理和線程池情況下會使用阻塞隊列

    方式拋出異常有返回值,不拋出異常阻塞等待超時等待
    添加addofferputoffer(,)
    移除removepolltakepoll(,)
    查看隊首元素elementpeek--
    public static void main(String[] args) {ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);System.out.println(arrayBlockingQueue.add("a"));System.out.println(arrayBlockingQueue.add("b"));System.out.println(arrayBlockingQueue.add("c"));System.out.println(arrayBlockingQueue.add("d"));//拋出異常System.out.println("===============");System.out.println(arrayBlockingQueue.remove());System.out.println(arrayBlockingQueue.remove());System.out.println(arrayBlockingQueue.remove());System.out.println(arrayBlockingQueue.remove()); //拋出異常} public static void main(String[] args) {ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);System.out.println(arrayBlockingQueue.offer("a"));System.out.println(arrayBlockingQueue.offer("b"));System.out.println(arrayBlockingQueue.offer("c"));System.out.println(arrayBlockingQueue.offer("d"));//返回falseSystem.out.println("===============");System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll()); //返回null} public static void main(String[] args) throws InterruptedException {ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);arrayBlockingQueue.put("a");arrayBlockingQueue.put("b");arrayBlockingQueue.put("c");arrayBlockingQueue.put("d");//隊列沒有位置了,一直阻塞System.out.println("===============");System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take()); //沒有這個元素,一直阻塞} public static void main(String[] args) throws InterruptedException {ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);arrayBlockingQueue.offer("a");arrayBlockingQueue.offer("b");arrayBlockingQueue.offer("c");arrayBlockingQueue.offer("d",1,TimeUnit.SECONDS);//等待1s后就退出System.out.println("===============");System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll(1,TimeUnit.SECONDS)); //等待1s后就退出}

    同步隊列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 3

    20.線程池

    三大方法,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//拒絕策略
    public ThreadPoolExecutor(int corePoolSize,//核心線程數int maximumPoolSize,//最大線程數long keepAliveTime,//超時等待時間TimeUnit unit,//超時單位BlockingQueue<Runnable> workQueue,//阻塞隊列ThreadFactory threadFactory,//線程工廠,一般不用動RejectedExecutionHandler handler//拒絕策略) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = 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.禁止指令重排

    指令重排:程序并不會按照寫的順序來執行,計算機會重排

    源代碼–>編譯器優化的重排–>指令并行也可能重排–>內存系統也會重排—>執行

    處理器在進行指令重排的時候,考慮:數據之間的依賴性!

    線程A線程B
    x=ay=b
    b=1a=2

    正常的結果:x=0,y=0;但是可能由于指令重排

    線程A線程B
    b=1a=2
    x=ay=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进阶笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。