预备知识、各种同步工具
1、synchronized + wait/notify
概括:Java内置的最基础的线程同步机制,基于对象监视器实现。
用途:用于简单的线程互斥和等待通知机制,如传统的生产者-消费者问题。
2、CountDownLatch
概括:一次性的事件等待器,通过倒计时控制线程执行顺序。
用途:等待多个并行任务全部完成后再继续执行,如初始化完成后启动主应用。
3、CompletableFuture
概括:声明式的异步编程框架,支持任务链式编排和组合。
用途:优雅地编排多个异步任务,处理复杂的异步依赖关系,如并行调用多个微服务。
4、Semaphore
概括:基于许可证的资源控制器,限制并发访问数量。
用途:控制对有限资源的并发访问,如数据库连接池、限流器。
5、ReentrantLock + Condition
概括:功能更丰富的显式锁,提供比synchronized更灵活的控制。
用途:需要高级同步特性的场景,如可中断锁、公平锁、多个等待条件。
6、BlockingQueue
BlockingQueue 是 Java 并发包中开箱即用的 "生产者 - 消费者" 解决方案,本质是对 ReentrantLock + Condition 的高级封装(比如 ArrayBlockingQueue 底层就是用这组工具实现的)
一、线程协作与同步
1、按序打印(Leetcode 1114)
给你一个类:
java
public class Foo {
public void first() { print("first"); }
public void second() { print("second"); }
public void third() { print("third"); }
}
三个不同的线程 A、B、C 将会共用一个 Foo 实例。
线程 A 将会调用 first() 方法
线程 B 将会调用 second() 方法
线程 C 将会调用 third() 方法
请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。
java
class Foo {
public Foo() {}
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
}
public void second(Runnable printSecond) throws InterruptedException {
printSecond.run();
}
public void third(Runnable printThird) throws InterruptedException {
printThird.run();
}
}
方法一:忙等待
优点:简单
缺点:空转CPU,可能会死循环
java
class Foo {
private volatile int x=0;
public Foo() { }
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
x++;
}
public void second(Runnable printSecond) throws InterruptedException {
while(x!=1){}
printSecond.run();
x++;
}
public void third(Runnable printThird) throws InterruptedException {
while(x!=2){}
printThird.run();
}
}
1)这里x++因为不是原子性操作,如果有多个线程同时调用first/second/third 会出现问题,
2)while循环里面可以加Thread.yield() 减少自旋的 CPU 消耗
java
class Foo {
private AtomicInteger x=new AtomicInteger(0);
public Foo() { }
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
x.incrementAndGet();
}
public void second(Runnable printSecond) throws InterruptedException {
while(x.get()!=1){Thread.yield();}
printSecond.run();
x.incrementAndGet();
}
public void third(Runnable printThird) throws InterruptedException {
while(x.get()!=2){Thread.yield();}
printThird.run();
}
}
方法二:synchronized + wait/notify
java
class Foo {
private int x=0;
private final Object lock=new Object();
public Foo() { }
public void first(Runnable printFirst) throws InterruptedException {
synchronized(lock){
printFirst.run();
x++;
lock.notifyAll();
}
}
public void second(Runnable printSecond) throws InterruptedException {
synchronized(lock){
while(x!=1){lock.wait();}
printSecond.run();
x++;
lock.notifyAll();
}
}
public void third(Runnable printThird) throws InterruptedException {
synchronized(lock){
while(x!=2){lock.wait();}
printThird.run();
lock.notifyAll();
}
}
}
方法三:CountDownLatch
java
class Foo {
private final CountDownLatch latch1 = new CountDownLatch(1);
private final CountDownLatch latch2 = new CountDownLatch(1);
public Foo() { }
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
latch1.countDown();
}
public void second(Runnable printSecond) throws InterruptedException {
latch1.await();
printSecond.run();
latch2.countDown();
}
public void third(Runnable printThird) throws InterruptedException {
latch2.await();
printThird.run();
}
}
方法四:Semaphore
java
class Foo {
private final Semaphore sem2 = new Semaphore(0);
private final Semaphore sem3 = new Semaphore(0);
public Foo() { }
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
sem2.release();
}
public void second(Runnable printSecond) throws InterruptedException {
sem2.acquire();
printSecond.run();
sem3.release();
}
public void third(Runnable printThird) throws InterruptedException {
sem3.acquire();
printThird.run();
}
}
方法五:ReentrantLock+Condition
java
class Foo {
private final Lock lock = new ReentrantLock();
private final Condition cond1 = lock.newCondition();
private final Condition cond2 = lock.newCondition();
private int flag = 0;
public Foo() { }
public void first(Runnable printFirst) throws InterruptedException {
lock.lock();
try{
printFirst.run();
flag++;
cond1.signal();
}finally{
lock.unlock();
}
}
public void second(Runnable printSecond) throws InterruptedException {
lock.lock();
try{
while (flag != 1) {
cond1.await();
}
printSecond.run();
flag++;
cond2.signal();
}finally{
lock.unlock();
}
}
public void third(Runnable printThird) throws InterruptedException {
lock.lock();
try{
while (flag != 2) {
cond2.await();
}
printThird.run();
}finally{
lock.unlock();
}
}
}
方法六:CompletableFuture
java
class Foo {
private CompletableFuture<Void> cf1 = new CompletableFuture<>();
private CompletableFuture<Void> cf2 = new CompletableFuture<>();
public void first(Runnable printFirst) {
printFirst.run();
cf1.complete(null); // 信号:first完成了
}
public void second(Runnable printSecond) {
cf1.join(); // 阻塞等待cf1完成
printSecond.run();
cf2.complete(null); // 信号:second完成了
}
public void third(Runnable printThird) {
cf2.join(); // 阻塞等待cf2完成
printThird.run();
}
}
2、交替打印(Leetcode 1115)
给你一个类:
java
class FooBar {
public void foo() {
for (int i = 0; i < n; i++) {
print("foo");
}
}
public void bar() {
for (int i = 0; i < n; i++) {
print("bar");
}
}
}
两个不同的线程将会共用一个 FooBar 实例:
线程 A 将会调用 foo() 方法,而
线程 B 将会调用 bar() 方法
请设计修改程序,以确保 "foobar" 被输出 n 次。
方法一:忙等待
java
class FooBar {
private int n;
public FooBar(int n) {
this.n = n;
}
private volatile boolean state = true;
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; ) {
if(state) {
printFoo.run();
i++;
state = false;
}else{
Thread.yield();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; ) {
if(!state) {
printBar.run();
i++;
state = true;
}else{
Thread.yield();
}
}
}
}
方法二:synchronized + wait/notify
java
class FooBar {
private int n;
public FooBar(int n) {
this.n = n;
}
private boolean state = true;
private final Object lock=new Object();
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized(lock){
while(!state){
lock.wait();
}
printFoo.run();
state=false;
lock.notifyAll();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n;i++) {
synchronized(lock){
while(state){
lock.wait();
}
printBar.run();
state=true;
lock.notifyAll();
}
}
}
}
方法三:Semaphore
java
class FooBar {
private int n;
public FooBar(int n) {
this.n = n;
}
private final Semaphore sem1 = new Semaphore(1);
private final Semaphore sem2 = new Semaphore(0);
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
sem1.acquire();
printFoo.run();
sem2.release();
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
sem2.acquire();
printBar.run();
sem1.release();
}
}
}
方法四:ReentrantLock+Condition
java
class FooBar {
private int n;
public FooBar(int n) {
this.n = n;
}
private final Lock lock = new ReentrantLock();
private final Condition cond = lock.newCondition();
private boolean flag = true;
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
lock.lock();
try{
while(!flag){
cond.await();
}
printFoo.run();
flag=false;
cond.signal();
}finally{
lock.unlock();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
lock.lock();
try{
while(flag){
cond.await();
}
printBar.run();
flag=true;
cond.signal();
}finally{
lock.unlock();
}
}
}
}
3、打印零与奇偶数(Leetcode 1116)
4、H2O生成(Leetcode 1117)
5、交替打印字符串(Leetcode 1195)
6、哲学家进餐(Leetcode 1226)
二、线程超时控制
1、启动一个线程完成某项任务比较耗时,对这个线程设置一个超时时间是2分钟,在2分钟时无论是否完成都需要退出它,如果提前完成就提前退出,如何实现
java
public class TimeoutThreadExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(()->{
try {
Thread.sleep(3000);
return "任务完成";
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); //不是题目要求,而是健壮性
return "任务中断";
}
});
try {
String s=future.get(2,TimeUnit.SECONDS);
System.out.println(s);
} catch (TimeoutException e) {
boolean cancelled = future.cancel(true);
System.out.println("取消" + (cancelled ? "成功" : "失败"));
}finally {
executor.shutdown();
}
}
}
三、分治并行
1、有一些内容需要一百万次For循环,然后需要你使用线程池,并发执行去降低他的耗时,最后把结果汇总。这个思路是什么
java
public class TimeoutThreadExample {
public static void main(String[] args) throws Exception {
int total = 1_000_000;
int threadCount = 8;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
int batchSize = total / threadCount;
List<Future<Integer>> futures = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
int start = i * batchSize;
int end = start+batchSize;
futures.add(executor.submit(() -> {
int sum=0;
for (int j = start; j < end; j++) {
sum += func();
}
return sum;
}));
}
int result = 0;
for (Future<Integer> future : futures) {
result += future.get(); // 阻塞等待每个结果
}
System.out.println("最终结果: " + result);
// 6. 关闭线程池
executor.shutdown();
}
public static int func(){
return 2;
}
}
四、多任务竞速
1、现在有三个访问天气预报的接口,他们访问的速度不同,想最快返回天气预报,怎么做
java
public class WeatherRaceBest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(3);
// 方法引用正常使用,无异常报错
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(WeatherRaceBest::getWeatherFromApi1, executor);
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(WeatherRaceBest::getWeatherFromApi2, executor);
CompletableFuture<String> cf3 = CompletableFuture.supplyAsync(WeatherRaceBest::getWeatherFromApi3, executor);
// 获取首个完成的结果
CompletableFuture<Object> fastestCf = CompletableFuture.anyOf(cf1, cf2, cf3);
String fastestResult = (String) fastestCf.get();
// 取消未完成的任务
if (!cf1.isDone()) cf1.cancel(true);
if (!cf2.isDone()) cf2.cancel(true);
if (!cf3.isDone()) cf3.cancel(true);
System.out.println("最快的API结果:" + fastestResult);
executor.shutdown(); // 关闭线程池
}
private static String getWeatherFromApi1() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 包装为运行时异常,无需声明throws
throw new RuntimeException("API1调用被中断", e);
}
return "【API1】北京:25℃ 晴";
}
private static String getWeatherFromApi2() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException("API2调用被中断", e);
}
return "【API2】北京:25℃ 晴";
}
private static String getWeatherFromApi3() {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
throw new RuntimeException("API3调用被中断", e);
}
return "【API3】北京:25℃ 晴";
}
}
六、设计模式
1、生产者-消费者模式
java
public class TimeoutThreadExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个有界阻塞队列,容量为 3
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
// 生产者线程
new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
String msg = "消息-" + i;
queue.put(msg); // 自动阻塞(如果队列满)
System.out.println("生产:" + msg);
//Thread.sleep(500); // 模拟生产速度
}
// 发送结束信号
queue.put("END");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// 消费者线程
new Thread(() -> {
try {
while (true) {
String msg = queue.take(); // 自动阻塞(如果队列空)
System.out.println("消费:" + msg);
Thread.sleep(10000); // 模拟生产速度
if ("END".equals(msg)) break; // 收到结束信号退出
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
2、单例模式 双重检测锁
java
public class Singleton {
// 🔴 关键1:用volatile修饰实例变量(禁止指令重排序)
private static volatile Singleton instance;
// 私有化构造方法,禁止外部new
private Singleton() {}
// 双重检查锁获取实例
public static Singleton getInstance() {
// 第一次检查:无锁,快速判断(大部分场景直接返回,提升性能)
if (instance == null) {
// 同步锁:保证只有一个线程进入初始化逻辑
synchronized (Singleton.class) {
// 第二次检查:防止多个线程等待锁后重复初始化
if (instance == null) {
// 初始化实例(volatile避免指令重排序导致的半初始化问题)
instance = new Singleton();
}
}
}
return instance;
}
}
3、令牌桶
java
class TokenBucket {
private long capacity; // 桶容量
private long tokens; // 当前令牌数
private long lastTime; // 上次更新时间
private long rate; // 填充速率(ms/个)
public TokenBucket(long capacity, long ratePerSecond) {
this.capacity = capacity;
this.tokens = capacity;
this.lastTime = System.currentTimeMillis();
this.rate = 1000 / ratePerSecond; // 每个令牌需要的毫秒数
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 补充令牌
tokens += (now - lastTime) / rate;
if (tokens > capacity) tokens = capacity;
lastTime = now;
// 检查令牌
if (tokens >= 1) {
tokens--;
return true;
}
return false;
}
}