目錄
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 包含
- Lock屬于 明鎖機制、需要手動的釋放鎖。
- 柵欄機制,圍欄,一個線程運行到一個點,線程停止運行,直到其它所有的線程都達到這個點,所有線程才重新運行,只能獲取一個任務,柵欄可以復用。
- 閉鎖機制
- 信號量機制
- 無鎖機制
- 交換機制
- 隊列機制
- 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