Java入門級教程16——JUC的安全并發包機制

目錄

1.JUC的安全并發包機制

1.1 包含

1.2 Barrier(柵欄)機制——CyclicBarrier(循環屏障)

1.2.1 定義

1.2.2 特性

1.2.1 模擬包車

1.2.2?模擬學生到齊上課

1.2.3?計算任務總耗時

1.3 CountDownLatch(閉鎖)機制

1.3.1 定義

1.3.2?特性

1.3.3?模擬用餐

1.3.4 變量自增

1.4 Semaphore(信號量)機制

1.4.1 定義

1.4.2 核心操作

1.4.2?模擬搶優惠券

1.4.3 模擬秒殺商品并將結果存入數據庫

1.5 無鎖機制

1.5.1 定義

1.5.2 變量自增


1.JUC的安全并發包機制

JUC(java.util.concurrent)是 JDK 1.5 后推出的核心并發工具包,旨在解決傳統多線程編程(如synchronized、Thread)的局限性(如靈活性低、效率差、功能單一),提供安全、高效、可擴展的并發控制能力。其核心機制圍繞 “線程同步”“資源控制”“任務調度” 三大場景設計。

1.1 包含

  1. Lock屬于 明鎖機制、需要手動的釋放鎖。
  2. 柵欄機制,圍欄,一個線程運行到一個點,線程停止運行,直到其它所有的線程都達到這個點,所有線程才重新運行,只能獲取一個任務,柵欄可以復用。
  3. 閉鎖機制
  4. 信號量機制
  5. 無鎖機制
  6. 交換機制
  7. 隊列機制
  8. JDK內置線程池機制,手寫線程池

1.2 Barrier(柵欄)機制——CyclicBarrier(循環屏障)

1.2.1 定義

柵欄機制(Barrier) 是一種用于實現 “多線程協同同步” 的工具,其核心作用是:

讓一組線程互相等待,直到所有線程都到達預設的 “柵欄點”,然后所有線程才會同時繼續執行后續邏輯;若配置了 “屏障動作”,則會在所有線程到達后、繼續執行前,自動觸發一次統一任務。

1.2.2 特性

① 循環性(Cyclic)—— 可重復使用
當一組線程完成一次柵欄協同后,柵欄可以通過 reset() 方法重置,再次用于下一組線程的協同(或同一組線程的下一次協同)。

② 線程互等 —— 雙向等待,缺一不可
柵欄的等待是 “雙向” 的:所有參與協同的線程(數量由 parties 參數指定)都必須調用 await() 方法到達柵欄點,否則已到達的線程會一直阻塞,不會單獨繼續執行。

③ 屏障動作(Barrier Action)—— 統一觸發全局任務
當所有線程到達柵欄點后,柵欄會自動執行一個預設的 Runnable 任務(屏障動作),執行完畢后,所有阻塞的線程才會被喚醒并繼續執行。

1.2.1 模擬包車

通過 CyclicBarrier(循環屏障)模擬了 “旅游包車,每滿 4 人發車” 的場景(28 人共需 7 輛車)。核心利用 CyclicBarrier 的 “線程等待 - 共同觸發” 特性,實現多線程間的同步協作。

package com.hy.chapter5;import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;/*** * 研學,旅游公司包車,一個車做4個同學,坐滿就發車; 總共有28個人,怎么控制和實現?**/public class Test {public static void main(String[] args) {CyclicBarrier cb = new CyclicBarrier(4, () -> {System.out.println("已經有4個同學了,就發車吧, 旅游車已經啟動出發");});for (int i = 0; i < 28; i++) {Runnable r = () -> {System.out.println("學生來報道............");// 設置一個CyclicBarrier的屏障點try {cb.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}};try {Thread.sleep(3000);} catch (InterruptedException e1) {e1.printStackTrace();}new Thread(r).start();}}}

輸出結果:

1.2.2?模擬學生到齊上課

通過 Java 并發工具類 CyclicBarrier 模擬了一個真實場景:3 名學生分別從家出發去學校(各自耗時不同),只有等所有學生都到達學校(線程全部到齊),老師才會開始上課(執行 “屏障動作”),之后學生再統一向老師問好。

① 學生任務類:StuRunnable(模擬學生趕路與等待)

package com.hy.chapter6;import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;public class StuRunnable implements Runnable {String name;int runTime;CyclicBarrier cb;public StuRunnable(String name, int runTime, CyclicBarrier cb) {this.name = name;this.runTime = runTime;this.cb = cb;}@Overridepublic void run() {System.out.println("每個學生從家到學校的時間是不一樣的,正在趕路....");try {Thread.sleep(this.runTime * 1000);// 設置一個屏蔽點,就是閾值,所有的學生的線程交互等待this.cb.await();System.out.println(this.name + ",同學們起立問好");} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}}

②?老師任務類:TeacherRunnable(模擬 “等學生到齊再上課”)

package com.hy.chapter6;public class TeacherRunnable implements Runnable {@Overridepublic void run() {System.out.println("老師等學生到齊,我們才開始上課");try {Thread.sleep(2000);System.out.println("同學們,正式上課");} catch (InterruptedException e) {e.printStackTrace();}}}

③?測試類:Test(初始化協同工具與啟動線程)

package com.hy.chapter6;import java.util.concurrent.CyclicBarrier;public class Test {public static void main(String[] args) {CyclicBarrier cb = new CyclicBarrier(3, new TeacherRunnable());new Thread(new StuRunnable("張三", 30, cb)).start();new Thread(new StuRunnable("李四", 20, cb)).start();new Thread(new StuRunnable("王五", 15, cb)).start();}
}

輸出結果:

每個學生從家到學校的時間是不一樣的,正在趕路....
每個學生從家到學校的時間是不一樣的,正在趕路....
每個學生從家到學校的時間是不一樣的,正在趕路....
老師等學生到齊,我們才開始上課
同學們,正式上課
李四,同學們起立問好
王五,同學們起立問好
張三,同學們起立問好

1.2.3?計算任務總耗時

于 Java 并發工具類 CyclicBarrier 實現的 “多任務協同計算” 案例:三個線程分別模擬 “數據庫調用”“郵件發送”“數據爬蟲” 任務,各自執行完成后,統一匯總計算三個任務的總耗時。

① 數據存儲類:Count(封裝任務結果與總和計算)

package com.hy.chapter7;public class Count {// 三個任務的耗時(分別由三個線程賦值)private int num1; // 數據庫任務耗時private int num2; // 郵件任務耗時private int num3; // 爬蟲任務耗時private  int sum;public int getNum1() {return num1;}public void setNum1(int num1) {this.num1 = num1;}public int getNum2() {return num2;}public void setNum2(int num2) {this.num2 = num2;}public int getNum3() {return num3;}public void setNum3(int num3) {this.num3 = num3;}public int getSum() {return this.num1+this.num2+this.num3;}public void setSum(int sum) {this.sum = sum;}}

② 工作線程類:OperatorThread(執行具體任務并等待協同)

package com.hy.chapter7;import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;// 實現Runnable接口:表示該類是可被線程執行的任務
public class OperatorThread implements Runnable {private String threadName; // 線程名稱(用于區分不同任務)private CyclicBarrier cb;  // 并發協同工具:CyclicBarrierprivate Count count;       // 數據存儲對象(用于存儲任務結果)// 構造器:初始化線程名稱、數據存儲對象、CyclicBarrierpublic OperatorThread(String threadName, Count count, CyclicBarrier cb) {this.threadName = threadName;this.count = count;this.cb = cb;}@Overridepublic void run() {// 1. 根據線程名稱,執行對應任務(模擬不同任務的耗時)if (this.threadName.contains("數據庫")) {try {Thread.sleep(6000); // 模擬“數據庫調用”耗時6秒this.count.setNum1(6000); // 將耗時存入count的num1} catch (InterruptedException e) {e.printStackTrace(); // 捕獲線程睡眠被中斷的異常}} else if (this.threadName.contains("郵件")) {try {Thread.sleep(8000); // 模擬“批量發送郵件”耗時8秒this.count.setNum2(8000); // 耗時存入count的num2} catch (InterruptedException e) {e.printStackTrace();}} else if (this.threadName.contains("爬蟲")) {try {Thread.sleep(12000); // 模擬“爬蟲批量數據”耗時12秒this.count.setNum3(12000); // 耗時存入count的num3} catch (InterruptedException e) {e.printStackTrace();}}// 2. 關鍵:等待所有線程完成任務(到達“屏障點”)try {System.out.println(threadName + " 完成任務,等待其他線程...");this.cb.await(); // 線程阻塞,直到所有線程都調用await()} catch (InterruptedException e) {e.printStackTrace(); // 線程在等待時被中斷} catch (BrokenBarrierException e) {e.printStackTrace(); // 屏障被破壞(如其他線程中斷/超時)}}
}

③ 測試類:Test(初始化并發工具與啟動線程)

package com.hy.chapter7;import java.util.concurrent.CyclicBarrier;public class Test {public static void main(String[] args) {// 1. 創建數據存儲對象(用于匯總三個任務的耗時)Count c = new Count();// 2. 創建CyclicBarrier:核心并發協同工具// 構造參數1:parties=3 → 需要3個線程到達屏障,才會觸發后續動作// 構造參數2:屏障動作 → 所有線程到達后,自動執行的任務(Lambda表達式)CyclicBarrier cb = new CyclicBarrier(3, () -> {// 屏障動作:計算并打印總耗時(僅當3個線程都完成任務后執行)System.out.println("三個工作的線程完成任務的總時間為:" + c.getSum());});// 3. 啟動三個線程,分別執行不同任務new Thread(new OperatorThread("調用數據庫", c, cb)).start();new Thread(new OperatorThread("批量發送郵件", c, cb)).start();new Thread(new OperatorThread("爬蟲批量數據", c, cb)).start();}
}

輸出結果:

調用數據庫 完成任務,等待其他線程...
批量發送郵件 完成任務,等待其他線程...
爬蟲批量數據 完成任務,等待其他線程...
三個工作的線程完成任務的總時間為:26000

1.3 CountDownLatch(閉鎖)機制

1.3.1 定義

閉鎖是一次性線程同步工具:通過一個 “計數器” 控制主線程(或等待線程)阻塞,直到所有子線程完成任務并調用countDown()將計數器減至 0,主線程才會喚醒并繼續執行。核心特性是一次性(計數器到 0 后無法重置,需重新創建對象)。

1.3.2?特性

① 一次性
計數器一旦減至 0,閉鎖的 “等待 - 喚醒” 邏輯就會永久生效,后續無法重置計數器(若需重復使用,需重新創建閉鎖對象)。這是閉鎖與 “循環屏障(CyclicBarrier)” 的核心區別(CyclicBarrier 支持計數器重置)。

② 單向等待
僅支持 “等待線程 → 被等待線程” 的單向依賴:等待線程(如主線程)必須等被等待線程(如子線程)全部完成;反之,被等待線程之間無需互相等待,也無需等待等待線程。

③ 非互斥性
閉鎖不限制被等待線程的執行順序,僅關注 “所有被等待線程是否完成”,不保證線程安全(若被等待線程操作共享資源,需額外加鎖)。

1.3.3?模擬用餐

通過 Java 并發工具類 CountDownLatch(閉鎖) 模擬了一個生活化場景:

5個人陸續到達餐廳,只有等所有 5人全部到齊后,大家才開始一起用餐。

package com.hy.chapter8;import java.util.concurrent.CountDownLatch;public class Test {public static void main(String[] args) {// 初始化CountDownLatch,計數器值=5(表示需要等待5個任務/線程完成)CountDownLatch cdl = new CountDownLatch(5);// 循環5次,創建5個線程(對應5個人)for(int i=0;i<5;i++) {// 定義每個線程要執行的任務(“人到達餐廳”的邏輯)Runnable r = ()->{// 1. 打印“當前線程(人)到達餐廳”的信息System.out.println(Thread.currentThread().getName()+",來吃飯.....");// 2. 關鍵:告訴閉鎖“我已到達”,計數器減1cdl.countDown();// 每次調用,計數器cdl的值 -=1};try {// 主線程休眠2秒(模擬“人陸續到達”,而非同時到)Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}// 啟動線程(“人出發去餐廳”)new Thread(r).start();}try {cdl.await(); // 主線程阻塞,直到計數器減至0} catch (InterruptedException e) {e.printStackTrace();}System.out.println("我們開始歡樂的用餐");}}

輸出結果:

Thread-0,來吃飯.....
Thread-1,來吃飯.....
Thread-2,來吃飯.....
Thread-3,來吃飯.....
Thread-4,來吃飯.....
我們開始歡樂的用餐

1.3.4 變量自增

① 使用閉鎖,不能保證線程安全

package com.hy.chapter2;import java.util.concurrent.CountDownLatch;public class Test {// volatile關鍵字也不能處理非原子化操作,不能保證線程安全// private volatile static int count = 0; private volatile static int count = 0;public static void inc() {try {Thread.sleep(1); // 毫秒// TimeUnit.MILLISECONDS.sleep(1); // 兩者等價count++;} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {// 閉鎖CountDownLatch cd = new CountDownLatch(100);for (int i = 0; i < 100; i++) {Runnable r = () -> {Test.inc();cd.countDown();};new Thread(r).start();}try {cd.await();System.out.println("總和為:" + Test.count);} catch (Exception e) {e.printStackTrace();}}}

輸出結果:

總和為:93

②?使用暗鎖

package com.hy.chapter2;import java.util.concurrent.CountDownLatch;public class Test1 {private static int count = 0;public static void inc() {try {Thread.sleep(1); // 毫秒// TimeUnit.MILLISECONDS.sleep(1); // 兩者等價// 暗鎖synchronized (Test1.class) {count++;}} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {// 閉鎖CountDownLatch cd = new CountDownLatch(100);for (int i = 0; i < 100; i++) {Runnable r = () -> {Test1.inc();cd.countDown();};new Thread(r).start();}try {cd.await();System.out.println("總和為:" + Test1.count);} catch (Exception e) {e.printStackTrace();}}}

輸出結果:

總和為:100

③ 使用明鎖

package com.hy.chapter2;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Test2 {private static int count = 0;static Lock lock = new ReentrantLock();public static void inc() {try {Thread.sleep(1); // 毫秒// TimeUnit.MILLISECONDS.sleep(1); // 兩者等價lock.lock();count++;lock.unlock();} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {// 閉鎖CountDownLatch cd = new CountDownLatch(100);for (int i = 0; i < 100; i++) {Runnable r = () -> {Test2.inc();cd.countDown();};new Thread(r).start();}try {cd.await();System.out.println("總和為:" + Test2.count);} catch (Exception e) {e.printStackTrace();}}}

輸出結果:

總和為:100

④ 注意:若使用明鎖時,不定義為靜態,則多次調用,拿到的都不是同一把鎖。

package com.hy.chapter2;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Test2 {private static int count = 0;public static void inc() {Lock lock = new ReentrantLock();try {Thread.sleep(1); // 毫秒lock.lock();count++;lock.unlock();} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {// 閉鎖CountDownLatch cd = new CountDownLatch(100);for (int i = 0; i < 100; i++) {Runnable r = () -> {Test2.inc();cd.countDown();};new Thread(r).start();}try {cd.await();System.out.println("總和為:" + Test2.count);} catch (Exception e) {e.printStackTrace();}}}

輸出結果:

總和為:88

⑤ 拓展:線程睡眠兩種不同寫法對比

// 同樣是暫停1秒,兩種寫法的可讀性對比
Thread.sleep(1000); // 需手動計算“1秒=1000毫秒”,不夠直觀
TimeUnit.SECONDS.sleep(1); // 直接表達“1秒”,可讀性更強

1.4 Semaphore(信號量)機制

1.4.1 定義

在并發編程中,信號量(Semaphore) 是一種用于控制 “并發訪問共享資源” 的同步機制,其核心作用是:通過維護一個 “許可(Permit)計數器”,限制同時訪問某一共享資源(或執行某一操作)的線程數量,本質是實現 “限流” 或 “資源占用控制”。

1.4.2 核心操作

  • P 操作(獲取許可):嘗試獲取 1 個許可,若計數器 > 0,則計數器減 1 并繼續執行;若計數器 = 0,則線程阻塞,進入等待隊列,直到有其他線程釋放許可。
  • V 操作(釋放許可):釋放 1 個許可,計數器加 1;若等待隊列中有線程,則喚醒其中一個線程(公平模式下喚醒等待最久的線程),讓其獲取許可繼續執行。

1.4.2?模擬搶優惠券

通過 Java 并發工具類 Semaphore(信號量) 模擬了一個 “優惠券搶購限流” 場景:

系統僅允許 3 個用戶同時搶優惠券(并發數控制),6 個用戶(線程)競爭這 3 個名額,搶完的用戶釋放名額后,其他等待的用戶才能繼續搶。

package com.hy.chapter9;import java.util.Random;
import java.util.concurrent.Semaphore;public class Test {public static void main(String[] args) {// 初始化Semaphore,許可數=3(表示最多允許3個線程同時獲取許可,即同時搶券)Semaphore s = new Semaphore(3);// 循環6次,創建6個線程(對應6個搶券用戶)for (int i = 0; i < 6; i++) {// 定義每個線程要執行的任務(“用戶搶券”的完整邏輯)Runnable r = () -> {try {// 1. 關鍵:獲取許可(搶“搶券資格”),沒有許可則阻塞s.acquire();// 2. 拿到許可后,執行“搶券”操作(打印搶券信息)System.out.println(Thread.currentThread().getName() + ", 搶優惠劵");// 3. 模擬“搶券后處理時間”(如提交訂單、校驗庫存),隨機0~20秒// new Random().nextInt(20):生成0~19的隨機數,×1000轉為毫秒Thread.sleep(new Random().nextInt(20) * 1000);// 4. 處理完成后,打印“離開現場”(表示放棄名額)System.out.println(Thread.currentThread().getName() + ", 離開現場");} catch (InterruptedException e) {e.printStackTrace(); // 線程在“等待許可”或“休眠”時被中斷} finally {// 5. 關鍵:釋放許可(歸還“搶券資格”,必須在finally中,確保無論是否異常都歸還)s.release();}};// 啟動線程(“用戶開始參與搶券”)new Thread(r).start();}}}

輸出結果:

Thread-0, 搶優惠劵
Thread-1, 搶優惠劵
Thread-2, 搶優惠劵
Thread-0, 離開現場
Thread-3, 搶優惠劵
Thread-2, 離開現場
Thread-4, 搶優惠劵
Thread-1, 離開現場
Thread-5, 搶優惠劵
Thread-4, 離開現場
Thread-3, 離開現場
Thread-5, 離開現場

1.4.3 模擬秒殺商品并將結果存入數據庫

實現了一個簡化版高并發秒殺系統,核心邏輯是:通過Semaphore(信號量)控制同時秒殺成功的用戶數量(限流),用多線程模擬 10 個用戶爭搶 3?個 “秒殺名額”,秒殺成功后異步寫入數據庫記錄。

① 新建Java Maven項目,在pom.xml文件中添加Maven依賴

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.24</version>
</dependency>

② 數據庫準備

CREATE TABLE t_p(pid INT PRIMARY KEY auto_increment,uname VARCHAR(20),pname VARCHAR(20)
)SELECT * FROM t_pDELETE FROM t_p

實現效果:

③?數據訪問層:Dao類(JDBC 操作數據庫)

Dao類是數據庫交互的核心,負責建立 MySQL 連接、執行數據插入操作,封裝了 JDBC 的完整流程,確保秒殺成功的用戶數據能持久化到數據庫。

package com.hy.chapter1;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;public class Dao {Connection conn; // 數據庫連接對象// 構造方法:初始化數據庫連接(加載驅動+建立連接)public Dao() {try {// 1. 加載MySQL 8.0+驅動(舊版本為com.mysql.jdbc.Driver)Class.forName("com.mysql.cj.jdbc.Driver");// 2. 建立數據庫連接:URL格式=jdbc:mysql://主機:端口/數據庫名conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mysql2025", // 數據庫地址(本地庫mysql2025)"root", "yourpassword"                  // 數據庫賬號密碼);} catch (ClassNotFoundException e) {e.printStackTrace(); // 驅動未找到(如依賴缺失)} catch (SQLException e) {e.printStackTrace(); // 連接異常(端口錯誤、賬號密碼錯誤等)}}// 業務方法:向t_p表插入秒殺成功的用戶數據(uname=用戶名,pname=商品名)public void add(String name, String pname) {// SQL語句:插入數據,用?占位符防止SQL注入String sql = "insert into t_p(uname,pname) values(?,?)";try {// 3. 創建PreparedStatement(預編譯SQL,安全高效)PreparedStatement pstmt = this.conn.prepareStatement(sql);// 4. 填充SQL參數(1對應第一個?,2對應第二個?)pstmt.setString(1, name);  // 用戶名(如“李0”“李1”)pstmt.setString(2, pname); // 商品名(固定為“鴻蒙電腦”)// 5. 執行更新操作(insert/update/delete用executeUpdate())pstmt.executeUpdate();} catch (SQLException e) {e.printStackTrace(); // SQL執行異常(表不存在、字段錯誤等)} finally {// 6. 關閉數據庫連接(必須在finally中,避免連接泄漏)if (null != this.conn) {try {this.conn.close();} catch (SQLException e) {e.printStackTrace();}}}}
}

④ 秒殺業務層:Shop類(基于 Semaphore 實現限流)

Shop類是秒殺業務的核心邏輯層,通過Semaphore(信號量)控制 “同時秒殺成功的用戶數量”,實現限流;同時處理 “秒殺結果判斷” 和 “異步寫入數據庫”。

package com.hy.chapter1;import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;// 秒殺業務類:控制秒殺名額,處理秒殺邏輯
public class Shop {private Semaphore s; // 信號量:用于控制秒殺成功的并發數// 構造方法:初始化信號量許可數(即“秒殺名額總數”)public Shop(int num) {s = new Semaphore(num); // num=3 → 最多3個用戶同時秒殺成功}// 秒殺核心方法:用戶發起秒殺請求public void userShoping(String name) {boolean flag = false; // 標記是否成功獲取秒殺許可try {// 1. 嘗試獲取1個許可(秒殺名額),最多等待1秒(超時則失敗)// tryAcquire(permits, timeout, unit):permits=1(1個名額),超時1秒flag = this.s.tryAcquire(1, TimeUnit.SECONDS);// 2. 判斷是否獲取到許可(秒殺成功/失敗)if (flag) {System.out.println(name + ",您秒殺成功,可以下單了");// 3. 異步寫入數據庫(開新線程,避免阻塞秒殺結果返回)Runnable r = () -> {Dao dao = new Dao(); // 創建數據訪問對象dao.add(name, "鴻蒙電腦"); // 插入秒殺記錄(用戶名+商品名)};new Thread(r).start(); // 啟動線程執行寫入操作// 4. 模擬“下單處理耗時”(如生成訂單、校驗庫存),阻塞1秒TimeUnit.SECONDS.sleep(1);} else {// 未獲取到許可(秒殺失敗)System.out.println(name + ",不好意思,秒殺沒有獲取通過");}} catch (InterruptedException e) {e.printStackTrace(); // 線程在“等待許可”或“休眠”時被中斷} finally {// 5. 僅當成功獲取許可的線程,才釋放許可(避免釋放未獲取的許可)if (flag) {this.s.release(); // 釋放1個許可,供其他用戶爭搶}}}
}

⑤ 用戶線程:User類(模擬秒殺用戶)

User類繼承Thread,代表一個秒殺用戶的線程,每個用戶線程啟動后,會調用Shop的userShoping方法發起秒殺請求。

package com.hy.chapter1;// 用戶線程類:每個線程代表一個參與秒殺的用戶
public class User extends Thread {private Shop shop; // 關聯的秒殺業務類(獲取秒殺名額)private String name; // 用戶名(如“李0”“李1”)// 構造方法:初始化用戶與秒殺業務的關聯public User(Shop shop, String name) {this.shop = shop;this.name = name;}// 線程執行邏輯:發起秒殺請求@Overridepublic void run() {this.shop.userShoping(name); // 調用Shop的秒殺方法}
}

⑥測試入口:Test類(模擬高并發秒殺)

Test類是程序入口,負責初始化秒殺環境、創建多用戶線程,模擬 10 個用戶爭搶 3?個秒殺名額的高并發場景。

package com.hy.chapter1;// 測試類:啟動秒殺系統,模擬多用戶并發秒殺
public class Test {public static void main(String[] args) {// 1. 創建Shop實例:秒殺名額=3(最多3人同時秒殺成功)Shop shop = new Shop(3);// 2. 循環創建10個用戶線程(10個用戶參與秒殺)for (int i = 0; i < 10; i++) {// 用戶名格式:“李0”“李1”...“李9”new User(shop, "李" + i).start(); // 啟動用戶線程,發起秒殺}}
}

輸出結果:

李8,您秒殺成功,可以下單了
李7,您秒殺成功,可以下單了
李0,您秒殺成功,可以下單了
李9,不好意思,秒殺沒有獲取通過
李1,不好意思,秒殺沒有獲取通過
李2,不好意思,秒殺沒有獲取通過
李5,不好意思,秒殺沒有獲取通過
李6,不好意思,秒殺沒有獲取通過
李3,不好意思,秒殺沒有獲取通過
李4,不好意思,秒殺沒有獲取通過

數據庫查詢:

1.5 無鎖機制

1.5.1 定義

?無鎖機制是一種不依賴傳統鎖(如synchronized、Lock)實現線程安全的并發控制方式,核心通過硬件級別的原子操作(如 CAS)保證共享資源修改的原子性,從而在高并發場景下獲得比鎖機制更高的性能。

無鎖機制的底層依賴CAS(Compare And Swap,比較并交換)?算法,這是一種由 CPU 直接支持的原子操作指令,能確保多線程環境下共享變量修改的安全性。

1.5.2 變量自增

通過AtomicInteger(原子整數類)實現線程安全的計數器自增操作,無需傳統鎖(如synchronized或Lock)即可保證并發安全;同時使用CountDownLatch實現主線程對所有子線程的等待同步。

package com.hy.chapter3;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;public class Test {// 原子鎖,就是無鎖// AtomicInteger 是 JUC(java.util.concurrent.atomic)包下的原子整數類,// 核心作用是在多線程環境下提供線程安全的整數操作(如自增、自減、賦值等),無需額外加鎖。private static AtomicInteger count = new AtomicInteger(0);public static void inc() {try {// 1. 線程休眠1毫秒:放大并發沖突概率Thread.sleep(1);// 2. 原子自增操作:count++的線程安全版本count.getAndIncrement();} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {// 創建了一個 CountDownLatch(閉鎖)實例// 讓一個或多個線程等待其他 100 個 “事件” 完成后再繼續執行CountDownLatch cd = new CountDownLatch(100);// CountDownLatch 本質是一個線程安全的遞減計數器,工作流程圍繞 “計數減為 0” 展開:// 初始化:通過 new CountDownLatch(100) 設置初始計數為 100,代表需要等待 100 個事件完成;// 事件完成:每個事件完成后,調用 cd.countDown() 方法,計數器會原子性減 1(從 100→99→...→0);// 等待喚醒:需要等待的線程(如主線程)調用 cd.await() 方法后進入阻塞狀態,直到計數器減為 0 時,所有阻塞線程被自動喚醒,繼續執行后續邏輯。for (int i = 0; i < 100; i++) {Runnable r = () -> {Test.inc();cd.countDown();};new Thread(r).start();}try {cd.await();System.out.println("總和為:" + Test.count);} catch (Exception e) {e.printStackTrace();}}}

輸出結果:

總和為:100

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96648.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96648.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96648.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【網絡通信】全面解析MAC地址:網絡設備的唯一標識

【網絡通信】全面解析MAC地址&#xff1a;網絡設備的唯一標識 文章目錄【網絡通信】全面解析MAC地址&#xff1a;網絡設備的唯一標識前言一、MAC 地址的定義&#xff1a;設備的 “網絡身份證”?二、MAC 地址的格式與組成&#xff1a;48 位的 “數字編碼”?三、MAC 地址的工作…

Perforce Klocwork 2025.2版本更新:默認啟用現代分析引擎、支持 MISRA C:2025 新規、CI構建性能提升等

Perforce Klocwork 現已更新至2025.2版本&#xff01;該版本增強了對 C/C的分析能力&#xff0c;提升了現代 C 分析的準確性&#xff0c;并改進了對源文件編碼的支持。該版本還為 MISRA C:2025 標準引入了新的分類體系&#xff0c;并增強了 Visual Studio Code 插件的可用性。 …

機器人馭風而行:低空經濟如何開啟智能新紀元【科普類】

新晉碼農一枚&#xff0c;小編會定期整理一些寫的比較好的代碼和知識點&#xff0c;作為自己的學習筆記&#xff0c;試著做一下批注和補充&#xff0c;轉載或者參考他人文獻會標明出處&#xff0c;非商用&#xff0c;如有侵權會刪改&#xff01;歡迎大家斧正和討論&#xff01;…

Java學習筆記四(繼承)

1 繼承繼承的實現&#xff1a;public class 子類 extends 父類 {… }注釋&#xff1a;子類可直接使用&#xff0c;父類&#xff08;保護&#xff0c;公開&#xff09;的屬性和方法優點&#xff1a;減少重復代碼&#xff0c;缺點&#xff1a;只能單繼承// 父類 public class Tes…

NAT技術:SNAT與DNAT區別詳解

1. 什么是NAT&#xff1f; 定義&#xff1a;NAT 是一種網絡技術&#xff0c;用于在私有網絡&#xff08;如家庭或企業局域網&#xff09; 與 公共網絡&#xff08;如互聯網&#xff09; 之間轉換IP地址。它允許使用私有IP地址的設備通過一個&#xff08;或多個&#xff09;公共…

java語言中,list<String>轉成字符串,逗號分割;List<Integer>轉字符串,逗號分割

java語言中&#xff0c;list<String 轉成字符串&#xff0c;逗號分割 在 Java 中&#xff0c;將 List<String> 轉成逗號分割的字符串有多種方法&#xff1a; 使用 String.join 方法 String.join 是 Java 8 引入的一個靜態方法&#xff0c;它可以方便地將集合中的元素用…

NineData云原生智能數據管理平臺新功能發布|2025年8月版

本月發布 11 項更新&#xff0c;其中重點發布 5項、功能優化 6 項。重點發布數據庫 DevOps - SQL 窗口支持 PolarDB 系列SQL 窗口新增支持 PolarDB PostgreSQL 與 PolarDB Oracle 數據源&#xff0c;擴展云原生數據庫管理能力。新增 AWS 數據源支持新增支持 AWS Aurora Postgre…

【ARDUINO】通過ESP8266連接WIFI,啟動TCP,接受TCP客戶端指令【測試中】

通過ESP8266連接WIFI&#xff0c;啟動TCP&#xff0c;接受TCP客戶端指令**記錄**2025年9月8日11:20:372025年9月9日08:45:342025年9月11日21:40:22**代碼**記錄 2025年9月8日11:20:37 【測試情況】 代碼可以跑到正確連接WIFI&#xff0c;也能獲得IP&#xff0c;但是啟動TCP服…

(網絡原理)核心知識回顧 網絡核心原理 get和post的理解 解析http 加密+請求和響應的一些關鍵字 Cookie和session 對密鑰的理解

目錄 核心知識回顧 網絡核心原理 get和post的理解 解析http 加密請求和響應的一些關鍵字 Cookie和session 對密鑰的理解 核心知識回顧 網絡編程---socket api UDP DatagramSocket DatagramPacket TCP ServerSocket Socket 1.讀寫數據通過Socket,通過Socket內置的 lnpu…

前端框架對比分析:離線PWA + Cloudflare Workers部署

目錄 概述 框架對比表格 詳細分析 1. Astro ????? **強烈推薦** 2. Next.js ???? **推薦** 3. Remix (現React Router) ????? **強烈推薦** 4. SvelteKit ???? **推薦** 5. Nuxt.js ??? **一般推薦** 6. Vite + React ??? **基礎選擇** 推薦方案 ?? …

9-10關于JS初學產生的問題

1.頁面添加加載完成事件監聽&#xff0c;頁面加載完成后&#xff0c;執行頁面初始化方法/函數; 這是什么意思 這句話描述的是前端開發中一種常見的操作&#xff1a;等待頁面完全加載完成后&#xff0c;再執行特定的初始化代碼。 簡單來說&#xff0c;就是要確保頁面上的所有元素…

項目中遇到pom文件里使用systemPath的例子記錄

項目中遇到pom文件里使用systemPath&#xff0c;很少見&#xff0c;問了下豆包&#xff0c;記錄下結果。在 Maven 的 pom.xml 中&#xff0c;<systemPath> 是 <dependency> 標簽內的一個可選配置&#xff0c;用于手動指定本地系統中某個依賴包&#xff08;通常是 J…

10、向量與矩陣基礎 - 深度學習的數學語言

學習目標:建立向量和矩陣的幾何直觀理解,掌握線性代數的核心概念,培養空間思維能力,為手搓大模型奠定扎實的數學基礎 想象一下,當你使用GPT進行對話時,每個詞匯都被轉換成高維向量,整個對話歷史變成一個巨大的矩陣。模型的"理解"過程,本質上就是在這個高維空…

【Python Tkinter】圖形用戶界面(GUI)開發及打包EXE指南

【Python Tkinter】圖形用戶界面&#xff08;GUI&#xff09;開發及打包EXE指南一、關于 Python Tkinter二、密碼生成器示例2.1 使用Python添加圖形用戶界面&#xff08;GUI&#xff09;2.2 使用工具PyInstaller將應用打包成exe文件三、總結一、關于 Python Tkinter Python Tk…

【設計模式】【觀察者模式】實例

一對多的統一監聽 —— 這就是 觀察者模式&#xff08;Observer Pattern&#xff09; 的經典應用場景。也就是說&#xff1a;一個事件源&#xff08;Subject&#xff09; → 可以注冊多個監聽器&#xff08;Observers&#xff09;&#xff1b;當事件發生時&#xff0c;一次性通…

C#測試調用OpenXml操作word文檔的基本用法

OpenXML SDK是微軟為高效處理Office文檔&#xff08;如Word、Excel&#xff09;而開發的開源.NET庫,它直接操作文檔內部的XML結構&#xff0c;無需安裝Office軟件即可實現文檔的創建、讀取和編輯,常用于服務器端批量生成報表、自動化文檔處理等場景&#xff0c;輕量且跨平臺。本…

照度傳感器考慮筆記

您好&#xff01;很高興為您解答關于照度計傳感器和設計的問題。這是一個非常專業且實際的話題。 一、照度計常用的照度傳感器類型 照度計的核心是光電探測器&#xff0c;其工作原理是將光信號轉換為電信號。目前主流的照度傳感器都屬于硅光電二極管&#xff08;Si Photodiode&…

C# Web API Mapster基本使用

安裝包&#xff1a;Mapster1.注冊MyRegister.Scan(); // 全局配置 //builder.Services.AddMapster(); // 需要安裝Mapster.DependencyInjection包 builder.Services.AddScoped<IMapper,Mapper>();2.配置&#xff08;可不進行配置直接使用也行&#xff09;public class My…

<數據集>無人機航拍人員搜救識別數據集<目標檢測>

數據集下載鏈接https://download.csdn.net/download/qq_53332949/91899456數據集格式&#xff1a;VOCYOLO格式 圖片數量&#xff1a;5755張 標注數量(xml文件個數)&#xff1a;5755 標注數量(txt文件個數)&#xff1a;5755 標注類別數&#xff1a;1 標注類別名稱&#xff…

STM32 開發(三十三)STM32F103 片內資源 —— 直接存儲 DMA 實戰 編碼詳解

??《上一篇》 ???《主目錄》 ???《下一篇》 文章目錄 一、基礎知識點 二、開發環境 三、STM32CubeMX相關配置 四、Vscode 代碼講解 ADC -DMA 采集溫度值 代碼解析 DAC -DMA 輸出 1KHZ 正弦波 代碼解析 五、結果演示 ADC -DMA 采集溫度值 結果演示 DAC -DMA 輸出 1KHZ 正…