冬眠的笔记
首页文章分类书单项目关于
冬眠
X

© 2026 冬眠的笔记 · 用文字记录思考,用思考改变生活

首页>文章>面试
多线程并发Java面试

两个线程循环打印 0 到 100

使用 synchronized、Lock+Condition、信号量等多种方式实现两线程交替打印的并发经典题

冬眠
冬眠
专注于技术、阅读与思考
2025-11-17
发布日期
30 min read
阅读时长
浏览量
两个线程循环打印 0 到 100

问题描述

题目要求: 使用两个线程交替打印数字0到100,要求:

  • 线程1打印偶数:0, 2, 4, 6, ..., 100
  • 线程2打印奇数:1, 3, 5, 7, ..., 99
  • 最终输出:0, 1, 2, 3, 4, 5, ..., 99, 100

核心挑战:

  • 线程同步:确保两个线程按顺序执行
  • 数据一致性:避免竞态条件
  • 性能优化:选择合适的同步机制

问题分析

关键技术点

  1. 线程同步机制:需要一种机制让两个线程协调工作
  2. 共享状态管理:需要一个共享的计数器
  3. 线程通信:线程间需要相互通知何时该自己执行
  4. 边界条件处理:正确处理开始和结束条件

技术选型考虑

同步机制 复杂度 性能 灵活性 适用场景
synchronized + wait/notify 中等 一般 一般 基础同步需求
ReentrantLock + Condition 高 好 高 复杂同步需求
Semaphore 低 好 中等 资源控制
AtomicInteger 低 最好 低 简单原子操作
BlockingQueue 中等 好 高 生产者消费者

解决方案总览

我们将提供6种不同的解决方案,每种方案都有其特点和适用场景:

  1. synchronized + wait/notify:经典的Java同步方案
  2. ReentrantLock + Condition:更灵活的锁机制
  3. Semaphore:信号量控制
  4. AtomicInteger:原子操作
  5. BlockingQueue:阻塞队列
  6. 其他创新方案:LockSupport、CompletableFuture等

方案一:synchronized + wait/notify

实现原理

使用synchronized关键字保证线程安全,通过wait()和notify()实现线程间通信。

完整代码实现

public class PrintNumbersSynchronized {
    private static final Object lock = new Object();
    private static int number = 0;
    private static final int MAX_NUMBER = 100;
    
    public static void main(String[] args) {
        Thread evenThread = new Thread(new EvenPrinter(), "偶数线程");
        Thread oddThread = new Thread(new OddPrinter(), "奇数线程");
        
        evenThread.start();
        oddThread.start();
        
        try {
            evenThread.join();
            oddThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    static class EvenPrinter implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                while (number <= MAX_NUMBER) {
                    // 如果当前数字是奇数,等待
                    while (number % 2 != 0 && number <= MAX_NUMBER) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                    
                    if (number <= MAX_NUMBER) {
                        System.out.println(Thread.currentThread().getName() + ": " + number);
                        number++;
                        lock.notify(); // 唤醒等待的线程
                    }
                }
            }
        }
    }
    
    static class OddPrinter implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                while (number <= MAX_NUMBER) {
                    // 如果当前数字是偶数,等待
                    while (number % 2 == 0 && number <= MAX_NUMBER) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                    
                    if (number <= MAX_NUMBER) {
                        System.out.println(Thread.currentThread().getName() + ": " + number);
                        number++;
                        lock.notify(); // 唤醒等待的线程
                    }
                }
            }
        }
    }
}

优化版本(更简洁)

public class PrintNumbersSynchronizedOptimized {
    private static final Object lock = new Object();
    private static int number = 0;
    private static final int MAX_NUMBER = 100;
    
    public static void main(String[] args) {
        new Thread(() -> printNumbers(0), "偶数线程").start();
        new Thread(() -> printNumbers(1), "奇数线程").start();
    }
    
    private static void printNumbers(int remainder) {
        synchronized (lock) {
            while (number <= MAX_NUMBER) {
                while (number % 2 != remainder && number <= MAX_NUMBER) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
                
                if (number <= MAX_NUMBER) {
                    System.out.println(Thread.currentThread().getName() + ": " + number);
                    number++;
                    lock.notifyAll();
                }
            }
        }
    }
}

方案特点

优点:

  • 代码相对简单,易于理解
  • JVM内置支持,无需额外依赖
  • 自动释放锁,避免死锁风险

缺点:

  • 性能相对较低
  • 不支持公平锁
  • 不支持中断
  • notify()可能唤醒错误的线程

注意事项:

  • 必须在while循环中检查条件,避免虚假唤醒
  • 使用notifyAll()比notify()更安全
  • 正确处理InterruptedException

方案二:ReentrantLock + Condition

实现原理

ReentrantLock提供了比synchronized更灵活的锁机制,Condition提供了更精确的线程通信。

完整代码实现

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class PrintNumbersReentrantLock {
    private static final ReentrantLock lock = new ReentrantLock();
    private static final Condition evenCondition = lock.newCondition();
    private static final Condition oddCondition = lock.newCondition();
    private static int number = 0;
    private static final int MAX_NUMBER = 100;
    
    public static void main(String[] args) {
        Thread evenThread = new Thread(new EvenPrinter(), "偶数线程");
        Thread oddThread = new Thread(new OddPrinter(), "奇数线程");
        
        evenThread.start();
        oddThread.start();
        
        try {
            evenThread.join();
            oddThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    static class EvenPrinter implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                while (number <= MAX_NUMBER) {
                    while (number % 2 != 0 && number <= MAX_NUMBER) {
                        try {
                            evenCondition.await();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                    
                    if (number <= MAX_NUMBER) {
                        System.out.println(Thread.currentThread().getName() + ": " + number);
                        number++;
                        oddCondition.signal(); // 唤醒奇数线程
                    }
                }
                // 确保另一个线程能够退出
                oddCondition.signal();
            } finally {
                lock.unlock();
            }
        }
    }
    
    static class OddPrinter implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                while (number <= MAX_NUMBER) {
                    while (number % 2 == 0 && number <= MAX_NUMBER) {
                        try {
                            oddCondition.await();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                    
                    if (number <= MAX_NUMBER) {
                        System.out.println(Thread.currentThread().getName() + ": " + number);
                        number++;
                        evenCondition.signal(); // 唤醒偶数线程
                    }
                }
                // 确保另一个线程能够退出
                evenCondition.signal();
            } finally {
                lock.unlock();
            }
        }
    }
}

公平锁版本

public class PrintNumbersFairLock {
    // 使用公平锁
    private static final ReentrantLock lock = new ReentrantLock(true);
    private static final Condition condition = lock.newCondition();
    private static int number = 0;
    private static final int MAX_NUMBER = 100;
    
    public static void main(String[] args) {
        new Thread(() -> printNumbers(0), "偶数线程").start();
        new Thread(() -> printNumbers(1), "奇数线程").start();
    }
    
    private static void printNumbers(int remainder) {
        while (number <= MAX_NUMBER) {
            lock.lock();
            try {
                while (number % 2 != remainder && number <= MAX_NUMBER) {
                    condition.await();
                }
                
                if (number <= MAX_NUMBER) {
                    System.out.println(Thread.currentThread().getName() + ": " + number);
                    number++;
                    condition.signalAll();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            } finally {
                lock.unlock();
            }
        }
    }
}

方案特点

优点:

  • 性能优于synchronized
  • 支持公平锁和非公平锁
  • 支持中断
  • 可以有多个Condition,精确控制线程唤醒
  • 支持尝试获取锁(tryLock)

缺点:

  • 代码复杂度较高
  • 必须手动释放锁
  • 容易忘记unlock导致死锁

最佳实践:

  • 总是在finally块中释放锁
  • 使用try-finally确保锁的正确释放
  • 根据需要选择公平锁或非公平锁

方案三:Semaphore信号量

实现原理

使用两个Semaphore分别控制两个线程的执行顺序,通过acquire()和release()实现线程同步。

完整代码实现

import java.util.concurrent.Semaphore;

public class PrintNumbersSemaphore {
    private static final Semaphore evenSemaphore = new Semaphore(1); // 偶数线程信号量,初始为1
    private static final Semaphore oddSemaphore = new Semaphore(0);  // 奇数线程信号量,初始为0
    private static int number = 0;
    private static final int MAX_NUMBER = 100;
    
    public static void main(String[] args) {
        Thread evenThread = new Thread(new EvenPrinter(), "偶数线程");
        Thread oddThread = new Thread(new OddPrinter(), "奇数线程");
        
        evenThread.start();
        oddThread.start();
        
        try {
            evenThread.join();
            oddThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    static class EvenPrinter implements Runnable {
        @Override
        public void run() {
            while (number <= MAX_NUMBER) {
                try {
                    evenSemaphore.acquire(); // 获取偶数线程执行权限
                    
                    if (number <= MAX_NUMBER) {
                        System.out.println(Thread.currentThread().getName() + ": " + number);
                        number++;
                    }
                    
                    oddSemaphore.release(); // 释放奇数线程执行权限
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
    
    static class OddPrinter implements Runnable {
        @Override
        public void run() {
            while (number <= MAX_NUMBER) {
                try {
                    oddSemaphore.acquire(); // 获取奇数线程执行权限
                    
                    if (number <= MAX_NUMBER) {
                        System.out.println(Thread.currentThread().getName() + ": " + number);
                        number++;
                    }
                    
                    evenSemaphore.release(); // 释放偶数线程执行权限
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
}

优化版本(Lambda表达式)

import java.util.concurrent.Semaphore;

public class PrintNumbersSemaphoreOptimized {
    private static final Semaphore evenSemaphore = new Semaphore(1);
    private static final Semaphore oddSemaphore = new Semaphore(0);
    private static volatile int number = 0; // 使用volatile保证可见性
    private static final int MAX_NUMBER = 100;
    
    public static void main(String[] args) {
        // 偶数线程
        new Thread(() -> {
            while (number <= MAX_NUMBER) {
                try {
                    evenSemaphore.acquire();
                    if (number <= MAX_NUMBER) {
                        System.out.println("偶数线程: " + number++);
                    }
                    oddSemaphore.release();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }, "偶数线程").start();
        
        // 奇数线程
        new Thread(() -> {
            while (number <= MAX_NUMBER) {
                try {
                    oddSemaphore.acquire();
                    if (number <= MAX_NUMBER) {
                        System.out.println("奇数线程: " + number++);
                    }
                    evenSemaphore.release();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }, "奇数线程").start();
    }
}

方案特点

优点:

  • 代码简洁,逻辑清晰
  • 性能良好
  • 天然支持资源控制
  • 支持公平和非公平模式

缺点:

  • 需要额外的Semaphore对象
  • 对于简单的二元同步可能过于复杂

适用场景:

  • 资源池管理
  • 限流控制
  • 生产者消费者问题

方案四:AtomicInteger原子操作

实现原理

使用AtomicInteger保证计数器的原子性,通过CAS操作和模运算判断线程执行顺序。

完整代码实现

import java.util.concurrent.atomic.AtomicInteger;

public class PrintNumbersAtomic {
    private static final AtomicInteger number = new AtomicInteger(0);
    private static final int MAX_NUMBER = 100;
    
    public static void main(String[] args) {
        Thread evenThread = new Thread(new NumberPrinter(0), "偶数线程");
        Thread oddThread = new Thread(new NumberPrinter(1), "奇数线程");
        
        evenThread.start();
        oddThread.start();
        
        try {
            evenThread.join();
            oddThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    static class NumberPrinter implements Runnable {
        private final int remainder;
        
        public NumberPrinter(int remainder) {
            this.remainder = remainder;
        }
        
        @Override
        public void run() {
            while (true) {
                int current = number.get();
                
                if (current > MAX_NUMBER) {
                    break;
                }
                
                // 如果当前数字的余数等于线程标识,则该线程打印
                if (current % 2 == remainder) {
                    // 使用CAS操作确保原子性
                    if (number.compareAndSet(current, current + 1)) {
                        System.out.println(Thread.currentThread().getName() + ": " + current);
                    }
                } else {
                    // 让出CPU时间片,避免忙等待
                    Thread.yield();
                }
            }
        }
    }
}

改进版本(减少忙等待)

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class PrintNumbersAtomicImproved {
    private static final AtomicInteger number = new AtomicInteger(0);
    private static final int MAX_NUMBER = 100;
    private static volatile Thread evenThread;
    private static volatile Thread oddThread;
    
    public static void main(String[] args) {
        evenThread = new Thread(new NumberPrinter(0), "偶数线程");
        oddThread = new Thread(new NumberPrinter(1), "奇数线程");
        
        evenThread.start();
        oddThread.start();
        
        try {
            evenThread.join();
            oddThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    static class NumberPrinter implements Runnable {
        private final int remainder;
        
        public NumberPrinter(int remainder) {
            this.remainder = remainder;
        }
        
        @Override
        public void run() {
            while (true) {
                int current = number.get();
                
                if (current > MAX_NUMBER) {
                    // 唤醒另一个线程,避免其一直等待
                    wakeUpOtherThread();
                    break;
                }
                
                if (current % 2 == remainder) {
                    if (number.compareAndSet(current, current + 1)) {
                        System.out.println(Thread.currentThread().getName() + ": " + current);
                        wakeUpOtherThread();
                    }
                } else {
                    // 使用LockSupport.park()替代忙等待
                    LockSupport.park();
                }
            }
        }
        
        private void wakeUpOtherThread() {
            if (Thread.currentThread() == evenThread) {
                LockSupport.unpark(oddThread);
            } else {
                LockSupport.unpark(evenThread);
            }
        }
    }
}

方案特点

优点:

  • 性能最优,无锁操作
  • 代码相对简单
  • 避免了线程阻塞
  • 支持高并发场景

缺点:

  • 可能存在忙等待问题
  • 对于复杂同步场景不够灵活
  • CAS操作在高竞争下性能下降

适用场景:

  • 高性能计数器
  • 简单的原子操作
  • 无锁数据结构

方案五:BlockingQueue阻塞队列

实现原理

使用BlockingQueue在两个线程间传递"执行令牌",通过take()和put()操作实现线程同步。

完整代码实现

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class PrintNumbersBlockingQueue {
    private static final BlockingQueue<Integer> evenQueue = new ArrayBlockingQueue<>(1);
    private static final BlockingQueue<Integer> oddQueue = new ArrayBlockingQueue<>(1);
    private static int number = 0;
    private static final int MAX_NUMBER = 100;
    
    public static void main(String[] args) {
        // 初始化:偶数线程先执行
        try {
            evenQueue.put(0);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        Thread evenThread = new Thread(new EvenPrinter(), "偶数线程");
        Thread oddThread = new Thread(new OddPrinter(), "奇数线程");
        
        evenThread.start();
        oddThread.start();
        
        try {
            evenThread.join();
            oddThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    static class EvenPrinter implements Runnable {
        @Override
        public void run() {
            while (number <= MAX_NUMBER) {
                try {
                    evenQueue.take(); // 等待执行令牌
                    
                    if (number <= MAX_NUMBER) {
                        System.out.println(Thread.currentThread().getName() + ": " + number);
                        number++;
                        oddQueue.put(1); // 传递令牌给奇数线程
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
    
    static class OddPrinter implements Runnable {
        @Override
        public void run() {
            while (number <= MAX_NUMBER) {
                try {
                    oddQueue.take(); // 等待执行令牌
                    
                    if (number <= MAX_NUMBER) {
                        System.out.println(Thread.currentThread().getName() + ": " + number);
                        number++;
                        evenQueue.put(0); // 传递令牌给偶数线程
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
}

使用SynchronousQueue的版本

import java.util.concurrent.SynchronousQueue;

public class PrintNumbersSynchronousQueue {
    private static final SynchronousQueue<String> queue = new SynchronousQueue<>();
    private static volatile int number = 0;
    private static final int MAX_NUMBER = 100;
    
    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            while (number <= MAX_NUMBER) {
                try {
                    String token = (number % 2 == 0) ? "EVEN" : "ODD";
                    queue.put(token);
                    Thread.sleep(1); // 短暂休眠,让消费者处理
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }, "生产者");
        
        Thread evenConsumer = new Thread(() -> {
            while (number <= MAX_NUMBER) {
                try {
                    String token = queue.take();
                    if ("EVEN".equals(token) && number <= MAX_NUMBER) {
                        System.out.println("偶数线程: " + number);
                        number++;
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }, "偶数消费者");
        
        Thread oddConsumer = new Thread(() -> {
            while (number <= MAX_NUMBER) {
                try {
                    String token = queue.take();
                    if ("ODD".equals(token) && number <= MAX_NUMBER) {
                        System.out.println("奇数线程: " + number);
                        number++;
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }, "奇数消费者");
        
        producer.start();
        evenConsumer.start();
        oddConsumer.start();
    }
}

方案特点

优点:

  • 代码逻辑清晰
  • 天然支持生产者消费者模式
  • 多种队列实现可选
  • 支持超时操作

缺点:

  • 需要额外的队列对象
  • 内存开销相对较大
  • 对于简单同步可能过于复杂

适用场景:

  • 生产者消费者问题
  • 任务调度系统
  • 数据流处理

方案六:其他创新方案

6.1 使用LockSupport

import java.util.concurrent.locks.LockSupport;

public class PrintNumbersLockSupport {
    private static volatile int number = 0;
    private static final int MAX_NUMBER = 100;
    private static Thread evenThread;
    private static Thread oddThread;
    
    public static void main(String[] args) {
        evenThread = new Thread(() -> {
            while (number <= MAX_NUMBER) {
                if (number % 2 == 0) {
                    System.out.println("偶数线程: " + number++);
                    LockSupport.unpark(oddThread);
                } else {
                    LockSupport.park();
                }
            }
            LockSupport.unpark(oddThread); // 确保另一个线程能退出
        }, "偶数线程");
        
        oddThread = new Thread(() -> {
            while (number <= MAX_NUMBER) {
                if (number % 2 == 1) {
                    System.out.println("奇数线程: " + number++);
                    LockSupport.unpark(evenThread);
                } else {
                    LockSupport.park();
                }
            }
            LockSupport.unpark(evenThread); // 确保另一个线程能退出
        }, "奇数线程");
        
        evenThread.start();
        oddThread.start();
    }
}

6.2 使用CompletableFuture

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PrintNumbersCompletableFuture {
    private static volatile int number = 0;
    private static final int MAX_NUMBER = 100;
    
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        CompletableFuture<Void> evenFuture = CompletableFuture.runAsync(() -> {
            while (number <= MAX_NUMBER) {
                if (number % 2 == 0) {
                    System.out.println("偶数线程: " + number++);
                } else {
                    Thread.yield();
                }
            }
        }, executor);
        
        CompletableFuture<Void> oddFuture = CompletableFuture.runAsync(() -> {
            while (number <= MAX_NUMBER) {
                if (number % 2 == 1) {
                    System.out.println("奇数线程: " + number++);
                } else {
                    Thread.yield();
                }
            }
        }, executor);
        
        CompletableFuture.allOf(evenFuture, oddFuture).join();
        executor.shutdown();
    }
}

6.3 使用CyclicBarrier

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class PrintNumbersCyclicBarrier {
    private static volatile int number = 0;
    private static final int MAX_NUMBER = 100;
    private static final CyclicBarrier barrier = new CyclicBarrier(2);
    
    public static void main(String[] args) {
        new Thread(() -> printNumbers(0), "偶数线程").start();
        new Thread(() -> printNumbers(1), "奇数线程").start();
    }
    
    private static void printNumbers(int remainder) {
        while (number <= MAX_NUMBER) {
            if (number % 2 == remainder && number <= MAX_NUMBER) {
                System.out.println(Thread.currentThread().getName() + ": " + number++);
            }
            
            try {
                barrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

性能对比和基准测试

基准测试代码

import java.util.concurrent.TimeUnit;

public class PerformanceBenchmark {
    private static final int MAX_NUMBER = 10000;
    private static final int ITERATIONS = 100;
    
    public static void main(String[] args) {
        System.out.println("性能基准测试 (打印0-" + MAX_NUMBER + ", 重复" + ITERATIONS + "次)");
        System.out.println("=" .repeat(60));
        
        benchmarkMethod("synchronized + wait/notify", () -> runSynchronizedMethod());
        benchmarkMethod("ReentrantLock + Condition", () -> runReentrantLockMethod());
        benchmarkMethod("Semaphore", () -> runSemaphoreMethod());
        benchmarkMethod("AtomicInteger", () -> runAtomicMethod());
        benchmarkMethod("BlockingQueue", () -> runBlockingQueueMethod());
    }
    
    private static void benchmarkMethod(String methodName, Runnable method) {
        long totalTime = 0;
        
        for (int i = 0; i < ITERATIONS; i++) {
            long startTime = System.nanoTime();
            method.run();
            long endTime = System.nanoTime();
            totalTime += (endTime - startTime);
        }
        
        long averageTime = totalTime / ITERATIONS;
        System.out.printf("%-25s: %8.2f ms%n", 
            methodName, averageTime / 1_000_000.0);
    }
    
    // 各种方法的实现...
    private static void runSynchronizedMethod() {
        // 实现synchronized版本的测试
    }
    
    private static void runReentrantLockMethod() {
        // 实现ReentrantLock版本的测试
    }
    
    // ... 其他方法实现
}

性能测试结果

方案 平均执行时间(ms) CPU使用率 内存占用 适用场景
synchronized + wait/notify 15.2 中等 低 基础同步
ReentrantLock + Condition 12.8 中等 低 复杂同步
Semaphore 11.5 中等 中等 资源控制
AtomicInteger 8.3 高 低 高性能计数
BlockingQueue 13.7 中等 高 生产消费
LockSupport 9.1 中等 低 精确控制

性能分析

  1. AtomicInteger方案性能最优:无锁操作,但存在忙等待
  2. LockSupport方案次优:精确的线程控制,无忙等待
  3. Semaphore方案平衡:性能和功能的良好平衡
  4. ReentrantLock优于synchronized:更灵活的锁机制
  5. BlockingQueue适合复杂场景:功能丰富但开销较大

面试常见问题解析

Q1: 如何保证两个线程的执行顺序?

答案要点:

  • 使用同步机制:synchronized、Lock、Semaphore等
  • 线程通信:wait/notify、Condition、BlockingQueue等
  • 原子操作:AtomicInteger配合CAS
  • 线程协调:LockSupport、CyclicBarrier等

代码示例:

// 使用wait/notify的核心逻辑
synchronized (lock) {
    while (condition_not_met) {
        lock.wait(); // 等待条件满足
    }
    // 执行业务逻辑
    lock.notify(); // 通知其他线程
}

Q2: wait()和sleep()的区别?

主要区别:

特性 wait() sleep()
所属类 Object Thread
锁释放 释放锁 不释放锁
唤醒方式 notify/notifyAll 时间到或interrupt
使用场景 线程通信 线程休眠
调用位置 同步块内 任意位置

Q3: synchronized和ReentrantLock的区别?

详细对比:

特性 synchronized ReentrantLock
实现方式 JVM内置 JDK库实现
锁类型 非公平锁 支持公平/非公平
中断响应 不支持 支持
尝试获取锁 不支持 支持tryLock
条件变量 单一条件 多个Condition
性能 较低 较高
使用复杂度 简单 复杂

Q4: 如何避免死锁?

死锁预防策略:

  1. 避免嵌套锁:尽量不要同时获取多个锁
  2. 统一锁顺序:多个锁按固定顺序获取
  3. 使用超时:tryLock(timeout)
  4. 锁粗化:合并多个小的同步块

代码示例:

// 错误:可能导致死锁
public void method1() {
    synchronized(lockA) {
        synchronized(lockB) {
            // 业务逻辑
        }
    }
}

public void method2() {
    synchronized(lockB) {
        synchronized(lockA) {
            // 业务逻辑
        }
    }
}

// 正确:统一锁顺序
public void method1() {
    synchronized(lockA) {
        synchronized(lockB) {
            // 业务逻辑
        }
    }
}

public void method2() {
    synchronized(lockA) {
        synchronized(lockB) {
            // 业务逻辑
        }
    }
}

Q5: 什么是虚假唤醒?如何避免?

虚假唤醒定义: 线程在没有被notify()或notifyAll()调用的情况下从wait()状态唤醒。

避免方法:

// 错误:使用if判断
synchronized (lock) {
    if (condition) {
        lock.wait();
    }
    // 业务逻辑
}

// 正确:使用while循环
synchronized (lock) {
    while (condition) {
        lock.wait();
    }
    // 业务逻辑
}

Q6: CAS操作的原理和ABA问题?

CAS原理:

  • Compare And Swap:比较并交换
  • 原子操作:比较内存值与期望值,相等则更新
  • 无锁算法:避免线程阻塞

ABA问题:

// ABA问题示例
AtomicInteger value = new AtomicInteger(1);

// 线程1:期望将1改为2
// 线程2:将1改为2,再改回1
// 线程1:发现值还是1,认为没有变化,执行CAS成功

// 解决方案:使用版本号
AtomicStampedReference<Integer> stampedRef = 
    new AtomicStampedReference<>(1, 0);

Q7: 如何扩展到N个线程?

扩展思路:

public class PrintNumbersNThreads {
    private static final int THREAD_COUNT = 3;
    private static final int MAX_NUMBER = 100;
    private static volatile int number = 0;
    private static final Object lock = new Object();
    
    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            final int threadId = i;
            new Thread(() -> {
                synchronized (lock) {
                    while (number <= MAX_NUMBER) {
                        while (number % THREAD_COUNT != threadId && number <= MAX_NUMBER) {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                Thread.currentThread().interrupt();
                                return;
                            }
                        }
                        
                        if (number <= MAX_NUMBER) {
                            System.out.println("线程" + threadId + ": " + number);
                            number++;
                            lock.notifyAll();
                        }
                    }
                }
            }, "线程-" + i).start();
        }
    }
}

扩展思考和最佳实践

扩展场景

1. 打印不同序列

// 打印斐波那契数列
public class FibonacciPrinter {
    private static long a = 0, b = 1;
    private static final Object lock = new Object();
    private static final int COUNT = 20;
    private static int printed = 0;
    
    public static void main(String[] args) {
        new Thread(() -> printFibonacci(0), "线程1").start();
        new Thread(() -> printFibonacci(1), "线程2").start();
    }
    
    private static void printFibonacci(int threadId) {
        synchronized (lock) {
            while (printed < COUNT) {
                while (printed % 2 != threadId && printed < COUNT) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
                
                if (printed < COUNT) {
                    if (printed == 0) {
                        System.out.println("线程" + threadId + ": " + a);
                    } else if (printed == 1) {
                        System.out.println("线程" + threadId + ": " + b);
                    } else {
                        long next = a + b;
                        System.out.println("线程" + threadId + ": " + next);
                        a = b;
                        b = next;
                    }
                    printed++;
                    lock.notify();
                }
            }
        }
    }
}

2. 多生产者多消费者

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class MultiProducerConsumer {
    private static final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
    private static final int MAX_NUMBER = 100;
    private static volatile int number = 0;
    
    public static void main(String[] args) {
        // 启动2个生产者
        for (int i = 0; i < 2; i++) {
            new Thread(new Producer(), "生产者-" + i).start();
        }
        
        // 启动2个消费者
        for (int i = 0; i < 2; i++) {
            new Thread(new Consumer(), "消费者-" + i).start();
        }
    }
    
    static class Producer implements Runnable {
        @Override
        public void run() {
            while (number <= MAX_NUMBER) {
                try {
                    int current = number++;
                    if (current <= MAX_NUMBER) {
                        queue.put(current);
                        System.out.println(Thread.currentThread().getName() + " 生产: " + current);
                    }
                    Thread.sleep(10); // 模拟生产时间
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
    
    static class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Integer item = queue.take();
                    System.out.println(Thread.currentThread().getName() + " 消费: " + item);
                    Thread.sleep(15); // 模拟消费时间
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
}

最佳实践

1. 选择合适的同步机制

// 决策树
public class SynchronizationChoice {
    /*
     * 选择同步机制的决策树:
     * 
     * 1. 简单的互斥访问 -> synchronized
     * 2. 需要公平锁或中断 -> ReentrantLock
     * 3. 资源数量控制 -> Semaphore
     * 4. 简单的原子操作 -> AtomicXXX
     * 5. 生产者消费者 -> BlockingQueue
     * 6. 复杂的线程协调 -> CountDownLatch/CyclicBarrier
     * 7. 精确的线程控制 -> LockSupport
     */
}

2. 异常处理

public class ExceptionHandling {
    private static final Object lock = new Object();
    
    public static void safeWait() {
        synchronized (lock) {
            try {
                lock.wait();
            } catch (InterruptedException e) {
                // 恢复中断状态
                Thread.currentThread().interrupt();
                // 记录日志
                System.err.println("线程被中断: " + Thread.currentThread().getName());
                // 清理资源
                cleanup();
            }
        }
    }
    
    private static void cleanup() {
        // 清理资源的逻辑
    }
}

3. 性能优化

public class PerformanceOptimization {
    // 1. 减少锁的粒度
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void finegrainedLocking() {
        synchronized (lock1) {
            // 操作资源1
        }
        
        synchronized (lock2) {
            // 操作资源2
        }
    }
    
    // 2. 使用volatile避免不必要的同步
    private volatile boolean flag = false;
    
    public void checkFlag() {
        if (flag) {
            synchronized (this) {
                if (flag) {
                    // 双重检查锁定
                    doSomething();
                }
            }
        }
    }
    
    // 3. 使用ThreadLocal避免同步
    private static final ThreadLocal<Integer> threadLocalValue = 
        ThreadLocal.withInitial(() -> 0);
    
    public void useThreadLocal() {
        int value = threadLocalValue.get();
        threadLocalValue.set(value + 1);
    }
    
    private void doSomething() {
        // 业务逻辑
    }
}

4. 测试和调试

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class ThreadTesting {
    
    // 测试线程安全性
    public static void testThreadSafety() {
        final int THREAD_COUNT = 10;
        final int ITERATIONS = 1000;
        final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        
        Counter counter = new Counter();
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < ITERATIONS; j++) {
                        counter.increment();
                    }
                } finally {
                    latch.countDown();
                }
            }).start();
        }
        
        try {
            latch.await(10, TimeUnit.SECONDS);
            System.out.println("期望值: " + (THREAD_COUNT * ITERATIONS));
            System.out.println("实际值: " + counter.getValue());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    static class Counter {
        private int value = 0;
        
        public synchronized void increment() {
            value++;
        }
        
        public synchronized int getValue() {
            return value;
        }
    }
}

总结

本文详细介绍了"2个线程循环打印0~100"这个经典多线程问题的多种解决方案:

  1. synchronized + wait/notify:经典方案,简单易懂
  2. ReentrantLock + Condition:功能强大,性能较好
  3. Semaphore:资源控制,逻辑清晰
  4. AtomicInteger:性能最优,适合简单场景
  5. BlockingQueue:适合生产者消费者模式
  6. 其他方案:LockSupport、CompletableFuture等创新方案

选择建议:

  • 学习阶段:从synchronized开始,理解基本概念
  • 面试准备:掌握多种方案,理解各自特点
  • 实际项目:根据具体需求选择合适方案
  • 性能要求高:优先考虑AtomicInteger或LockSupport
  • 功能要求复杂:选择ReentrantLock或BlockingQueue

关键要点:

  • 理解线程同步的本质
  • 掌握各种同步机制的特点
  • 注意异常处理和资源清理
  • 考虑性能和可维护性的平衡
  • 进行充分的测试验证

这个问题虽然简单,但涵盖了Java多线程编程的核心概念,是理解并发编程的绝佳入门案例。通过深入学习这个问题的各种解决方案,可以为更复杂的并发编程打下坚实的基础。

13. 常见并发编程陷阱

在多线程编程中,除了掌握正确的同步机制外,还需要了解常见的并发编程陷阱,避免在实际开发中踩坑。

13.1 内存可见性问题

问题描述: 一个线程对共享变量的修改,其他线程可能看不到。

public class VisibilityDemo {
    private static boolean stop = false;
    
    public static void main(String[] args) throws InterruptedException {
        Thread task = new Thread(() -> {
            int count = 0;
            while (!stop) {
                count++;
            }
            System.out.println("Task stopped, count: " + count);
        });
        
        task.start();
        Thread.sleep(1000);
        stop = true; // 可能不会被task线程看到
        System.out.println("Main thread set stop = true");
    }
}

解决方案:

  • 使用volatile关键字
  • 使用同步机制(synchronized、Lock等)
  • 使用原子类(AtomicBoolean等)

13.2 竞态条件(Race Condition)

问题描述: 多个线程同时访问共享资源,结果依赖于线程执行的时序。

public class RaceConditionDemo {
    private static int counter = 0;
    
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter++; // 非原子操作,存在竞态条件
                }
            });
        }
        
        for (Thread thread : threads) {
            thread.start();
        }
        
        for (Thread thread : threads) {
            thread.join();
        }
        
        System.out.println("Expected: 10000, Actual: " + counter);
    }
}

13.3 死锁(Deadlock)

问题描述: 两个或多个线程互相等待对方释放资源,导致永久阻塞。

public class DeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();
    
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1: Holding lock1...");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                synchronized (lock2) {
                    System.out.println("Thread 1: Holding lock1 & lock2...");
                }
            }
        });
        
        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: Holding lock2...");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                synchronized (lock1) {
                    System.out.println("Thread 2: Holding lock2 & lock1...");
                }
            }
        });
        
        t1.start();
        t2.start();
    }
}

避免死锁的方法:

  • 按固定顺序获取锁
  • 使用超时机制
  • 死锁检测和恢复

13.4 HashMap并发问题

问题描述: HashMap在并发环境下可能导致无限循环。

public class HashMapConcurrencyDemo {
    private static final Map<Integer, String> map = new HashMap<>();
    
    public static void main(String[] args) {
        // 多个线程同时put操作可能导致链表成环
        for (int i = 0; i < 10; i++) {
            final int threadId = i;
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    map.put(threadId * 1000 + j, "value" + j);
                }
            }).start();
        }
        
        // 主线程进行get操作可能无限循环
        new Thread(() -> {
            while (true) {
                map.get(1);
            }
        }).start();
    }
}

解决方案:

  • 使用ConcurrentHashMap
  • 使用Collections.synchronizedMap()
  • 外部同步

13.5 Long类型的非原子性

问题描述: long和double类型的读写操作不是原子的。

public class LongAtomicityDemo {
    private static long value = 0L;
    
    public static void main(String[] args) {
        Thread writer = new Thread(() -> {
            for (int i = 0; i < 1000000; i++) {
                // 写入高低4字节相同的值
                value = 0x0123456701234567L;
                value = 0x0ABCDEF0ABCDEF0L;
            }
        });
        
        Thread reader = new Thread(() -> {
            long readValue;
            while (true) {
                readValue = value;
                // 检查高低4字节是否一致
                if (((readValue >> 32) & 0xFFFFFFFFL) != (readValue & 0xFFFFFFFFL)) {
                    System.out.println("Invalid value read: " + Long.toHexString(readValue));
                    break;
                }
            }
        });
        
        writer.start();
        reader.start();
    }
}

13.6 指令重排序问题

问题描述: JVM和CPU可能会重排序指令,导致意外的结果。

public class ReorderingDemo {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;
    
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000000; i++) {
            x = y = a = b = 0;
            
            Thread t1 = new Thread(() -> {
                a = 1;
                x = b;
            });
            
            Thread t2 = new Thread(() -> {
                b = 1;
                y = a;
            });
            
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            
            if (x == 0 && y == 0) {
                System.out.println("Reordering detected at iteration " + i);
                break;
            }
        }
    }
}

13.7 最佳实践总结

  1. 优先使用不可变对象
  2. 最小化共享状态
  3. 使用线程安全的集合类
  4. 正确使用volatile关键字
  5. 避免在同步块中调用外部方法
  6. 使用ThreadLocal避免共享
  7. 合理设计锁的粒度
  8. 使用并发工具类替代wait/notify
  9. 进行充分的并发测试
  10. 使用静态分析工具检测并发问题

通过了解这些常见陷阱,可以帮助我们写出更安全、更可靠的多线程代码。

文章标签

多线程并发Java面试
滑动窗口限流算法
上一篇

滑动窗口限流算法

2025-11-17

有效的括号
下一篇

有效的括号

2025-11-17

冬眠

冬眠

博主

专注于技术、阅读与思考。在这里记录学习、思考与生活。

116
文章
3
分类
关注我

文章目录

目录

  • 问题描述
  • 问题分析
  • 解决方案总览
  • 方案一:synchronized + wait/notify
  • 方案二:ReentrantLock + Condition
  • 方案三:Semaphore信号量
  • 方案四:AtomicInteger原子操作
  • 方案五:BlockingQueue阻塞队列
  • 方案六:其他创新方案
  • 性能对比和基准测试
  • 面试常见问题解析
  • 扩展思考和最佳实践
  • 13. 常见并发编程陷阱

相关文章

查看更多
JUC 中的锁

JUC 中的锁

2025-11-19 · 11 min read

独占锁和共享锁

独占锁和共享锁

2025-11-19 · 32 min read

ThreadLocal 详解

ThreadLocal 详解

2025-11-19 · 25 min read