目录
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