目录
[1.1 包含](#1.1 包含)
[1.2 Barrier(栅栏)机制------CyclicBarrier(循环屏障)](#1.2 Barrier(栅栏)机制——CyclicBarrier(循环屏障))
[1.2.1 定义](#1.2.1 定义)
[1.2.2 特性](#1.2.2 特性)
[1.2.1 模拟包车](#1.2.1 模拟包车)
[1.2.2 模拟学生到齐上课](#1.2.2 模拟学生到齐上课)
[1.2.3 计算任务总耗时](#1.2.3 计算任务总耗时)
[1.3 CountDownLatch(闭锁)机制](#1.3 CountDownLatch(闭锁)机制)
[1.3.1 定义](#1.3.1 定义)
[1.3.2 特性](#1.3.2 特性)
[1.3.3 模拟用餐](#1.3.3 模拟用餐)
[1.3.4 变量自增](#1.3.4 变量自增)
[1.4 Semaphore(信号量)机制](#1.4 Semaphore(信号量)机制)
[1.4.1 定义](#1.4.1 定义)
[1.4.2 核心操作](#1.4.2 核心操作)
[1.4.2 模拟抢优惠券](#1.4.2 模拟抢优惠券)
[1.4.3 模拟秒杀商品并将结果存入数据库](#1.4.3 模拟秒杀商品并将结果存入数据库)
[1.5 无锁机制](#1.5 无锁机制)
[1.5.1 定义](#1.5.1 定义)
[1.5.2 变量自增](#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 的 "线程等待 - 共同触发" 特性,实现多线程间的同步协作。
java
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(模拟学生赶路与等待)
java
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;
}
@Override
public 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(模拟 "等学生到齐再上课")
java
package com.hy.chapter6;
public class TeacherRunnable implements Runnable {
@Override
public void run() {
System.out.println("老师等学生到齐,我们才开始上课");
try {
Thread.sleep(2000);
System.out.println("同学们,正式上课");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
③ 测试类:Test(初始化协同工具与启动线程)
java
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(封装任务结果与总和计算)
java
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(执行具体任务并等待协同)
java
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; // 并发协同工具:CyclicBarrier
private Count count; // 数据存储对象(用于存储任务结果)
// 构造器:初始化线程名称、数据存储对象、CyclicBarrier
public OperatorThread(String threadName, Count count, CyclicBarrier cb) {
this.threadName = threadName;
this.count = count;
this.cb = cb;
}
@Override
public 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(初始化并发工具与启动线程)
java
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人全部到齐后,大家才开始一起用餐。
java
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. 关键:告诉闭锁"我已到达",计数器减1
cdl.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 变量自增
① 使用闭锁,不能保证线程安全
java
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
② 使用暗锁
java
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
③ 使用明锁
java
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
④ 注意:若使用明锁时,不定义为静态,则多次调用,拿到的都不是同一把锁。
java
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
⑤ 拓展:线程睡眠两种不同写法对比
java
// 同样是暂停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 个名额,抢完的用户释放名额后,其他等待的用户才能继续抢。
java
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依赖
XML
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.24</version>
</dependency>
② 数据库准备
sql
CREATE TABLE t_p(
pid INT PRIMARY KEY auto_increment,
uname VARCHAR(20),
pname VARCHAR(20)
)
SELECT * FROM t_p
DELETE FROM t_p
实现效果:

③ 数据访问层:Dao类(JDBC 操作数据库)
Dao类是数据库交互的核心,负责建立 MySQL 连接、执行数据插入操作,封装了 JDBC 的完整流程,确保秒杀成功的用户数据能持久化到数据库。
java
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(信号量)控制 "同时秒杀成功的用户数量",实现限流;同时处理 "秒杀结果判断" 和 "异步写入数据库"。
java
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方法发起秒杀请求。
java
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;
}
// 线程执行逻辑:发起秒杀请求
@Override
public void run() {
this.shop.userShoping(name); // 调用Shop的秒杀方法
}
}
⑥测试入口:Test类(模拟高并发秒杀)
Test类是程序入口,负责初始化秒杀环境、创建多用户线程,模拟 10 个用户争抢 3 个秒杀名额的高并发场景。
java
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实现主线程对所有子线程的等待同步。
java
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