[Java][華為云Java編程創造營][學習筆記][第三階段][05_Java多線程實戰][05_JUC并發包]

      網友投稿 615 2025-04-02

      5.1,線程的ThreadLocal本地緩存對象


      ThreadLocal線程范圍內的共享變量:

      線程范圍內的共享變量,每個線程只能自己的數據,不能訪問別的線程的數據。

      每個線程調用全局ThreadLocal對象的set方法,就相當于往其內部的map中增加一條記錄,key分別是各自的線程,value是各自的set方法傳進去的值。

      ThreadLocal以內存換安全

      5.2,線程的volatile關鍵字

      volatile關鍵字可以用來修飾字段(成員變量),就是告知程序任何對該變量的訪問均需要從共享內存中獲取,而對它的改變必須同步刷新回共享內存,它能保證所有線程對變量訪問的可見性。

      class UserThread extends Thread { private volatile boolean flag = false; public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } @Override public void run() { System.out.println(Thread.currentThread().getName() + ",線程正在運行"); while (flag) { } System.out.println(Thread.currentThread().getName() + ",線程運行結束"); } } public class Test { public static void main(String[] args) { UserThread ut = new UserThread(); ut.start(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } ut.setFlag(false); /* * 輸出結果 * Thread-0,線程正在運行 Thread-0,線程運行結束 * */ } }

      volatile的作用:使變量在多個線程之間可見,但是無法保證原子性。

      需要注意的是一般volatile用于只針對多個線程可見的變量操作,并不能代替synchronized的同步功能。

      public class ThreadVolatile extends Thread { public static volatile int n = 0; @Override public void run() { for (int i = 0; i < 10; i++) { try { n += 1; //為了使運行結果更隨機,延遲3毫秒 sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws Exception { Thread threads[] = new Thread[100]; for (int i = 0; i < threads.length; i++) { //建立100個線程 threads[i] = new ThreadVolatile(); } for (int i = 0; i < threads.length; i++) { //運行剛才建立的100個線程 threads[i].start(); } for (int i = 0; i < threads.length; i++) { //100個線程都執行完后繼續 threads[i].join(); } System.out.println("n= " + ThreadVolatile.n);//n= 909 } }

      5.3,線程池的作用和應用

      線程池的作用:

      降低資源消耗。

      通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。

      提高響應速度。

      當任務到達時,任務可以不需要等到線程創建就能立即執行。

      提高線程的可管理性。

      線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

      線程池的應用

      場景:請求頻繁,考慮到服務的并發問題,如果每個請求來到后,服務都為它啟動一個線程,那么這對服務的資源可能會造成很大的浪費。

      5.4,線程的同步工具類CountDownLatch

      CountDownLatch同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。

      CountDownLatch類是一個同步計數器,構造時傳入int參數,該參數就是計數器的初始值,每調用countDown()方法,計數器減1,計數器大于0時,await()方法會阻塞程序繼續執行。

      由于調用了countDown()方法,所以在當前計數到達0之前,await()方法會一直受阻塞。之后,會釋放所有等待的線程,await()的所有后續調用都將立即返回。這種現象只出現一次,計數無法被重置。一個線程或者多個,等待另外N個線程完成某個事情之后才能執行。

      CountDownLatch最重要的方法是countDown()和await()

      import java.util.concurrent.CountDownLatch; class UserThread1 extends Thread { private CountDownLatch cd; private int sum1; public UserThread1(CountDownLatch cd) { this.cd = cd; } @Override public void run() { for (int i = 0; i <= 100; i++) { sum1 += i; } cd.countDown(); } public int getSum1() { return sum1; } } class UserThread2 extends Thread { private CountDownLatch cd; private int sum2; public UserThread2(CountDownLatch cd) { this.cd = cd; } @Override public void run() { for (int i = 101; i <= 200; i++) { sum2 += i; } cd.countDown(); } public int getSum2() { return sum2; } } public class Test { public static void main(String[] args) { CountDownLatch cd = new CountDownLatch(2); UserThread1 u1 = new UserThread1(cd); UserThread2 u2 = new UserThread2(cd); u1.start(); u2.start(); try { cd.await(); int sum = u1.getSum1() + u2.getSum2(); System.out.println("兩個線程計算的和為:" + sum);//兩個線程計算的和為:20100 } catch (InterruptedException e) { e.printStackTrace(); } } }

      5.5,線程的同步工具類CyclicBarrier

      CyclicBarrier是一個同步輔助類,允許一組線程互相等待,直到到達某個公共屏障點(common barrier point)。因為該barrier在釋放等待線程后可以重用,所以稱它為循環的barrier。

      5.6,線程的同步工具類semaphore

      Semaphore是一個計數信號量,它的本質是一個共享鎖,是基于AQS實現的,通過state變量來實現共享。通過調用acquire方法,對state值減一,當調用release對state值加一。當state變量小于0時,在AQS隊列中阻塞等待。

      import java.util.concurrent.Semaphore; class Car extends Thread { private Address address; public Car(Address address) { this.address = address; } @Override public void run() { this.address.autoCar(); } } class Address { //每次停車的數量 private int num; //信號量 private Semaphore sm; public Address(int num) { this.num = num; sm = new Semaphore(this.num); } public void autoCar() { try { //加鎖 sm.acquire(); System.out.println(Thread.currentThread().getName() + ",進入停車場"); //模擬停車時間 Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + ",離開停車場"); //釋放鎖 sm.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Test { public static void main(String[] args) { Address address = new Address(2); for (int i = 0; i < 5; i++) { new Car(address).start(); } /* * 輸出結果 * Thread-0,進入停車場 Thread-1,進入停車場 Thread-1,離開停車場 Thread-0,離開停車場 Thread-2,進入停車場 Thread-3,進入停車場 Thread-3,離開停車場 Thread-2,離開停車場 * */ } }

      5.7,線程的交換類Exchanger

      Exchanger(交換者)是一個用于線程間協作的工具類,Exchanger用于進行線程間的數據交換。

      它提供一個同步點,在這個同步點,兩個線程可以交換彼此的數據。

      這兩個線程通過exchanger()交換數據。

      如果第一個線程先執行exchange()方法,它會一直等待第二個線程也執行exchange(),當兩個線程都到達同步點時,這兩個線程就可以交換數據,將本線程生產出來的數據傳遞給對方。

      import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { private static final Exchanger expr = new Exchanger<>(); private static ExecutorService threadpool = Executors.newFixedThreadPool(2); public static void main(String[] args) { threadpool.execute(new Runnable() { @Override public void run() { //A錄入銀行流水數據 try { String A = "銀行流水A"; String B = expr.exchange(A); System.out.println("Thread A:" + B); } catch (InterruptedException e) { e.printStackTrace(); } } }); threadpool.execute(new Runnable() { @Override public void run() { //B錄入銀行流水數據 try { String B = "銀行流水B"; String A = expr.exchange(B); System.out.println("Thread B:" + A); } catch (InterruptedException e) { e.printStackTrace(); } } }); threadpool.shutdown(); /* * 輸出結果 * Thread B:銀行流水A Thread A:銀行流水B * */ } }

      5.8,線程的Fork-Join機制

      Fork/Join框架是Java7提供一個用于并行執行任務的框架,是一個把大任務分割成若干個小任務,最終匯總每個小任務結果后得到大任務結果的框架。

      分治法:把一個規模大的問題劃分為規模較小的子問題,然后分而治之,最后合并子問題的解得到原問題的解。

      Fork/Join案例:

      import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask; class CountTask extends RecursiveTask { private int start; private int end; //計算任務量的值 private static final int TASKSIZE = 2; private static int count = 0; public CountTask(int start, int end) { this.start = start; this.end = end; } @Override protected Integer compute() { int sum = 0; System.out.println("開啟線程進行計算:" + count++); boolean flag = (this.end - this.start) <= TASKSIZE; //如果是小于等于任務的值 if (flag) { //沒有必要拆分任務計算 for (int i = start; i <= end; i++) { sum += i; } } else { //要進行拆分任務計算 System.out.println("這個任務需要進行拆分任務進行計算...." + Thread.currentThread().getName()); //任務大于值,分裂為兩個任務 10+1/2 int middle = (start + end) / 2; CountTask countTask1 = new CountTask(start, middle); CountTask countTask2 = new CountTask(middle + 1, end); //開啟線程計算分布式任務 invokeAll(countTask1, countTask2); Integer taskSum1 = countTask1.join(); Integer taskSum2 = countTask2.join(); //結果合并 sum = taskSum1 + taskSum2; } return sum; } } public class Test { public static void main(String[] args) { //分布式計算的池子 ForkJoinPool forkJoinPool = new ForkJoinPool(); //初始化設置任務 CountTask countTask = new CountTask(1, 10); //分布式計算任務,提交任務 ForkJoinTask forkJoinTask = forkJoinPool.submit(countTask); //最后得到計算結果 try { System.out.println(forkJoinTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }

      5.9,線程的組合案例購票

      /* * 線程并發購票 * */ class Tickets { private int allowance; public Tickets(int allowance) { this.allowance = allowance; } public int getAllowance() { return allowance; } public void setAllowance(int allowance) { this.allowance = allowance; } //購票方法 public void buyTickets(int num) { synchronized (this) { int before = this.allowance; int after = before - num; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setAllowance(after); } } } class CustomerRunnable implements Runnable { private Tickets tickets; public CustomerRunnable(Tickets tickets) { this.tickets = tickets; } @Override public void run() { tickets.buyTickets(1); System.out.println(Thread.currentThread().getName() + "購票成功,余票:" + tickets.getAllowance()); } } public class Test { public static void main(String[] args) { //創建tickets對象 Tickets tickets = new Tickets(5); //創建CustomerRunnable數組 CustomerRunnable[] customerRunnables = new CustomerRunnable[5]; //創建Thread數組 Thread[] threads = new Thread[5]; //創建100個CustomerRunnable對象 for (int i = 0; i < customerRunnables.length; i++) { customerRunnables[i] = new CustomerRunnable(tickets); } //創建100個線程 for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(customerRunnables[i]); threads[i].start(); } /* * 輸出結果 * Thread-1購票成功,余票:4 Thread-4購票成功,余票:3 Thread-2購票成功,余票:2 Thread-0購票成功,余票:1 Thread-3購票成功,余票:0 * */ } }

      5.10,線程的組合案例購物

      import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /* * 線程并發秒殺購物 * */ class Shop { //信號量 private Semaphore semaphore; public Shop(int num) { //實例化信號量,每次可以有5個線程獲得許可 semaphore = new Semaphore(num); } //用戶去搶購 public void userShopping(String name) { boolean flag = false; try { //怎么限流搶購,什么時候算搶購成功,還需要入庫 flag = this.semaphore.tryAcquire(1, TimeUnit.SECONDS); if (flag) { System.out.println(name + ",搶購成功,可以下單了"); TimeUnit.SECONDS.sleep(1); } else { System.out.println(name + ",對不起,搶購沒有成功,請重試。。。"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (flag) { //搶購成功,一定要注意釋放 this.semaphore.release(); } } } } class User extends Thread { private String sname; private Shop shop; public User(String sname, Shop shop) { super(); this.sname = sname; this.shop = shop; } @Override public void run() { this.shop.userShopping(sname); } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public Shop getShop() { return shop; } public void setShop(Shop shop) { this.shop = shop; } } public class Test { public static void main(String[] args) { Shop shop = new Shop(5); for (int i = 0; i < 5; i++) { new User("張" + i, shop).start(); } } /* * 輸出結果 * 張0,搶購成功,可以下單了 張4,搶購成功,可以下單了 張2,搶購成功,可以下單了 張1,搶購成功,可以下單了 張3,搶購成功,可以下單了 * */ }

      5.11,線程的鎖的synchronized和Lock、volatile區別

      synchronized和volatile區別:

      volatile關鍵字解決的是變量在多個線程之間的可見性;而synchronized關鍵字解決的是多個線程之間訪問共享資源的同步性。

      volatile只能用于修飾變量,而synchronized可以修飾方法,以及代碼塊。

      多線程訪問volatile不會發生阻塞,而synchronized會出現阻塞。

      volatile能保證變量在多個線程之間的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有內存和公有內存中的數據做同步。

      synchronized和Lock區別:

      Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現。

      synchronized在發生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現象的出現;而Lock在發生異常時,如果沒有主動通過unlock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖。

      Lock可以提高多個線程進行讀操作的效率(讀寫鎖)。

      5.12,線程的讀寫分離機制

      ReadWriteLock顧名思義,是讀寫鎖:它維護了一對相關的鎖"讀取鎖"和"寫入鎖",一個用于讀取操作,一個用于寫入操作。

      讀取鎖用于只讀操作,它是共享鎖,能同時被多個線程獲取。

      [Java][華為云Java編程創造營][學習筆記][第三階段][05_Java多線程實戰][05_JUC并發包]

      寫入鎖用于寫入操作,它是獨占鎖,寫入鎖只能被一個線程鎖獲取。

      不能同時存在讀取鎖和寫入鎖!可以讀/讀,但不能讀/寫,寫/寫。

      Java 多線程

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

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

      上一篇:溯源角度看:進程間互斥
      下一篇:Qt6-鼠標移動窗口
      相關文章
      中文无码亚洲精品字幕| 亚洲国产老鸭窝一区二区三区| 亚洲人成电影在线天堂| 亚洲午夜久久久影院| 国产精品V亚洲精品V日韩精品 | 亚洲乱码卡三乱码新区| 久久亚洲精精品中文字幕| 亚洲性猛交xx乱| 亚洲网红精品大秀在线观看| 亚洲午夜未满十八勿入| 亚洲嫩草影院久久精品| 婷婷亚洲综合五月天小说| 亚洲免费精彩视频在线观看| 亚洲av日韩av高潮潮喷无码| 亚洲国产精品久久久久婷婷软件| 亚洲AV本道一区二区三区四区| 久久被窝电影亚洲爽爽爽| 亚洲精品美女久久777777| 亚洲va中文字幕无码久久不卡 | 亚洲AV中文无码字幕色三| 久久久久亚洲精品美女| 99ri精品国产亚洲| 亚洲另类精品xxxx人妖| 亚洲资源最新版在线观看| 亚洲中文无码卡通动漫野外| 亚洲AV无码一区二区三区牲色| 国产亚洲精品AAAA片APP| 亚洲精品国产高清嫩草影院| 国产亚洲色视频在线| 亚洲av永久无码精品网站| 亚洲伊人久久大香线蕉苏妲己| 亚洲欧洲日产国码二区首页| 亚洲国产成人精品激情| 亚洲av永久无码| 久久激情亚洲精品无码?V| 亚洲va无码va在线va天堂| 亚洲春色另类小说| 亚洲变态另类一区二区三区| 亚洲第一黄色网址| 亚洲乱码一区二区三区在线观看| 午夜亚洲国产理论秋霞|