Java 基础知识点——并发

目录

❓思考题

1️⃣基础知识点

1. Java并发基础概念

2. 线程与进程

3. 线程的生命周期

4. 创建线程的方式

5. 线程池(Executor)

6. 线程同步

7. 死锁(Deadlock)

8. 并发集合

9. 原子变量

10. 并发工具类

11. 并发编程设计模式

12. 并发调度与线程优先级

13. 并发测试与性能调优

14. Java 8中的并发新特性

15. 并发性能优化

16. 线程安全的设计原则

2️⃣面试相关

1. 什么是死锁?如何避免死锁?

2. 什么是volatile关键字?它的作用是什么?

3. 什么是synchronized关键字?它如何工作?

4. ReentrantLock与synchronized有什么区别?

5. 什么是CountDownLatch,如何使用它?

6. 什么是线程池,Java如何实现线程池管理?

7. Java中如何处理并发中的线程安全问题?

8. 解释一下什么是“ABA问题”,并提供解决方案。

9. ExecutorService的submit()与invokeAll()方法有什么区别?

10. 什么是ThreadLocal,它是如何工作的?

11. Java中如何实现线程间的通信?

12. Future与Callable的区别是什么?

13. ConcurrentHashMap 和 Hashtable 的区别?

14. Java 中的线程池有哪些实现?

15. 什么是线程池的工作原理?

16. Java 中的线程安全集合有哪些?

💡思考题参考解答


可以先看思考题,然后思考,然后再看看一些知道点,若是有补充的地方,欢迎👏指正🎉

具体如下:

❓思考题

题目描述:

 现有函数 printNumber 可以用一个整数参数调用,并输出该整数到控制台。

  • 例如,调用 printNumber(7) 将会输出 7 到控制台。

给你类 ZeroEvenOdd 的一个实例,该类中有三个函数:zeroeven 和 odd 。ZeroEvenOdd 的相同实例将会传递给三个不同线程:

  • 线程 A:调用 zero() ,只输出 0
  • 线程 B:调用 even() ,只输出偶数
  • 线程 C:调用 odd() ,只输出奇数

修改给出的类,以输出序列 "010203040506..." ,其中序列的长度必须为 2n 。

实现 ZeroEvenOdd 类:

  • ZeroEvenOdd(int n) 用数字 n 初始化对象,表示需要输出的数。
  • void zero(printNumber) 调用 printNumber 以输出一个 0 。
  • void even(printNumber) 调用printNumber 以输出偶数。
  • void odd(printNumber) 调用 printNumber 以输出奇数。

示例 1:

输入:n = 2
输出:"0102"
解释:三条线程异步执行,其中一个调用 zero(),另一个线程调用 even(),最后一个线程调用odd()。正确的输出为 "0102"。

示例 2:

输入:n = 5
输出:"0102030405"

提示:1 <= n <= 1000

其他相关思考题目:多线程题目 

1️⃣基础知识点

1. Java并发基础概念

  • 并发(Concurrency):多个任务在同一时间段内交替执行,并不一定是同时执行。例如,操作系统可能通过时间片轮转来让多个线程“同时”执行。

  • 并行(Parallelism):多个任务在同一时间真正并行执行,通常需要多核处理器支持。

2. 线程与进程

  • 进程(Process):操作系统资源分配的基本单位,每个进程有自己的地址空间。

  • 线程(Thread):是进程中的一个执行单元,线程间共享进程的资源(如内存)。线程是操作系统调度的基本单位。

3. 线程的生命周期

线程的生命周期包括:

  • 新建(New):线程对象已经创建,但尚未启动。

  • 就绪(Runnable):线程已准备好执行,但等待操作系统调度。

  • 运行(Running):线程被操作系统调度并开始执行。

  • 阻塞(Blocked):线程在等待资源(如I/O、锁)时被挂起。

  • 死亡(Dead):线程执行完毕或被中止。

4. 创建线程的方式

Java提供了最常见两种的创建线程的方式:

  • 继承Thread类:重写run()方法并调用start()方法启动线程。

  • 实现Runnable接口:实现run()方法并传递给Thread对象。

参考:Java中创建线程的多种方式实例-CSDN博客

其他内容:https://blog.csdn.net/qq_43544074/article/details/134988612 

5. 线程池(Executor)

线程池是提高多线程应用性能的关键工具,避免频繁的线程创建和销毁。

  • Executor框架:Java提供了Executor接口及其实现,如ThreadPoolExecutor

    • Executors.newFixedThreadPool(int nThreads):创建一个固定大小的线程池。

    • Executors.newCachedThreadPool():创建一个根据需要创建新线程的(缓存)线程池。

    • Executors.newSingleThreadExecutor():创建一个单线程的线程池。

    • Executors.newScheduledThreadPool(int corePoolSize):支持延迟任务的线程池。

线程池管理通过线程池大小、任务队列、线程工厂等参数进行调节。

6. 线程同步

线程同步是解决多个线程并发访问共享资源时数据不一致的问题。

  • synchronized关键字:Java提供了synchronized关键字,用于修饰方法或代码块,确保同一时间只有一个线程可以访问同步代码块。

    public synchronized void synchronizedMethod() { // critical section }

    • 类锁和实例锁synchronized修饰实例方法时是实例锁,修饰静态方法时是类锁。

  • ReentrantLockjava.util.concurrent.locks.ReentrantLock提供了比synchronized更灵活的锁机制,支持显式锁和解锁操作。

    Lock lock = new ReentrantLock();
     try { 
    lock.lock(); // critical section 
    } finally { 
    lock.unlock(); 
    }
特性synchronizedReentrantLock
语法内置关键字需要显式导入和实例化
灵活性不能中断等待可以中断等待
超时支持不支持支持尝试获取锁并设置超时
公平性非公平锁支持公平锁和非公平锁
  • 同步块:更细粒度的同步控制 

Object lock = new Object();

public void syncBlock() {
    synchronized (lock) {
        // 关键代码段
    }
}

同步工具

1. CountDownLatch

允许一个或多个线程等待其他线程完成操作:

CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> { latch.countDown(); }).start();
latch.await(); // 主线程等待

2. CyclicBarrier

多个线程到达屏障后一起执行:

CyclicBarrier barrier = new CyclicBarrier(3);
new Thread(() -> { barrier.await(); }).start();
barrier.await();

3. Semaphore

控制同时访问某一资源的线程数量:

Semaphore semaphore = new Semaphore(3);
semaphore.acquire();
try {
    // 访问资源
} finally {
    semaphore.release();
}

7. 死锁(Deadlock)

死锁是指两个或多个线程相互等待对方释放资源,导致程序无法继续执行。避免死锁的策略包括:

  • 避免嵌套锁(锁的顺序应该一致)。

  • 使用定时锁尝试(tryLock()方法)。

  • 限制锁的持有时间。

8. 并发集合

Java并发包(java.util.concurrent)提供了线程安全的集合类,这些集合类比传统的集合类(如VectorHashtable)具有更高的并发性能。

  • CopyOnWriteArrayList:写时复制,适用于读多写少的场景。

List<String> list = new CopyOnWriteArrayList<>();
list.add("item");
  • ConcurrentHashMap:高效的并发哈希表,支持分段锁定。

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key", "value");
  • BlockingQueue:线程安全的队列,常用于生产者消费者问题。

BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("item"); // 生产者
String item = queue.take(); // 消费者

9. 原子变量

原子变量是对基本数据类型进行线程安全操作的工具。Java提供了java.util.concurrent.atomic包中的一些原子类:

  • AtomicIntegerAtomicLongAtomicReference等。

这些类提供了原子操作,确保不会发生线程干扰。

10. 并发工具类

Java并发工具类(位于java.util.concurrent包中)帮助开发者更方便地实现并发操作。

  • CountDownLatch:使一个或多个线程等待其他线程完成操作。

    CountDownLatch latch = new CountDownLatch(1); latch.await(); // 等待 latch.countDown(); // 减少计数

  • CyclicBarrier:使一组线程互相等待,直到所有线程都到达某个公共屏障点。

  • Semaphore:控制访问资源的线程数目,类似于信号量。

  • Exchanger:用于交换数据的线程同步点。

11. 并发编程设计模式

  • 生产者-消费者模式:通过队列实现生产者与消费者之间的协调。

  • 读写锁模式(Read-Write Lock):允许多个线程并发读取,但只有一个线程可以写入。

    • ReentrantReadWriteLock支持读写锁。

12. 并发调度与线程优先级

  • 线程优先级:通过Thread.setPriority(int priority)设置线程优先级,但并非所有操作系统都支持。

  • Thread.sleep():使线程暂停执行指定的时间。

  • yield():让当前线程主动让出CPU使用权,通常是为了增强程序的响应性。

13. 并发测试与性能调优

  • 线程泄漏:线程没有被正确关闭或结束,导致系统资源浪费。

  • ThreadLocal:为每个线程提供独立的变量副本,避免了线程间的数据竞争。

14. Java 8中的并发新特性

  • CompletableFuture:提供了更灵活的异步编程支持,支持链式调用和异常处理。

  • Stream API:Stream的并行处理(parallelStream())可以让数据流在多个线程中并行处理,适用于大量数据的处理。

15. 并发性能优化

  • 减少锁竞争:通过锁分段(例如ConcurrentHashMap)或使用无锁编程(如CAS)来减少竞争。

  • 减少上下文切换:避免线程频繁切换,尽量让线程在获得锁后保持较长时间的计算。

  • 合适的线程池配置:合理配置线程池大小,避免创建过多的线程导致过高的上下文切换。

16. 线程安全的设计原则

  1. 尽量减少锁的范围:仅锁定必要的代码块。
  2. 使用无锁算法:如 ConcurrentHashMap 和 Atomic 类。
  3. 避免死锁:确保资源获取顺序一致。
  4. 优先使用并发集合:如 ConcurrentHashMap 替代手动同步。


2️⃣面试相关

1. 什么是死锁?如何避免死锁?

参考回答:
死锁是指两个或多个线程因争夺资源而互相等待,导致程序无法继续执行。死锁发生的四个必要条件是:

  • 互斥:每个资源要么是被一个线程占用,要么是可用的。

  • 持有并等待:一个线程持有一个资源并等待获取其他资源。

  • 不剥夺(不可抢占):线程已经获得的资源在没有使用完之前,不能被其他线程剥夺。

  • 循环等待:线程之间形成一种循环等待资源的关系。

如何避免死锁

  1. 避免嵌套锁:所有线程按相同顺序请求锁,以避免循环等待。

  2. 使用tryLock()方法:通过ReentrantLocktryLock()方法尝试获取锁,如果获取不到锁可以选择放弃或者尝试其他措施。

  3. 设定锁的超时:设置锁的最大等待时间,如果超时未能获得锁,线程放弃并重新尝试。

  4. 减少锁的粒度:避免对大范围的资源进行锁定,尽量缩小锁的范围。


2. 什么是volatile关键字?它的作用是什么?

参考回答:
volatile是Java中的一个关键字,它用于保证变量在不同线程之间的可见性。当一个变量被声明为volatile时,确保所有线程都能看到这个变量的最新值,而不会读取到线程的本地缓存中的值。

作用

  • 保证可见性:当一个线程修改了volatile变量的值,其他线程能立即看到修改后的值。

  • 禁止指令重排volatile保证了在多线程环境下的“有序性”。即volatile变量的读取和写入操作不能与其他操作交换顺序。

注意

  • volatile并不能保证复合操作(如i++)的原子性,如果需要复合操作的原子性,应该使用AtomicInteger等类。

  • volatile只是保证可见性,不能保证原子性。


3. 什么是synchronized关键字?它如何工作?

参考回答:
synchronized是Java中一个用来实现线程同步的关键字。它保证在同一时刻只有一个线程能够执行被synchronized修饰的代码块或方法,从而避免多个线程并发访问共享资源时发生数据不一致。

如何工作

  • 同步方法:在方法声明时使用synchronized关键字,表示方法中的代码块是同步的。

    public synchronized void syncMethod() { // synchronized code }

    这个同步方法的锁是当前实例对象(对于实例方法)或者类对象(对于静态方法)。

  • 同步代码块:使用synchronized关键字修饰代码块,可以指定一个锁对象进行同步。

    synchronized (lockObject) { // synchronized code }

原理
当一个线程访问synchronized修饰的代码时,首先需要获取该方法或代码块的锁对象。当该锁被其他线程持有时,其他线程无法访问该代码块,直到锁被释放。


4. ReentrantLocksynchronized有什么区别?

参考回答:

  • 可重入性ReentrantLock是可重入的,意味着同一个线程可以多次获得同一把锁;synchronized也是可重入的。

  • 锁的灵活性ReentrantLocksynchronized提供了更多的功能,如定时锁(tryLock())、公平锁、可以通过lockInterruptibly()响应中断等。

  • 性能ReentrantLock的性能在高并发时通常优于synchronized,因为它提供了更多的调节控制方式(例如公平锁)。

  • 可中断性ReentrantLock支持中断锁的等待(lockInterruptibly()),而synchronized在等待锁时无法中断。

  • 可实现公平性ReentrantLock支持公平锁(通过构造函数传入true),确保线程按请求的顺序获取锁,而synchronized没有公平性保证。


5. 什么是CountDownLatch,如何使用它?

参考回答:
CountDownLatch是一个同步工具类,用于在一个或多个线程中等待其他线程完成操作后再继续执行。它维护一个计数器,每次调用countDown()方法会减少计数器的值,直到计数器的值为零时,调用await()方法的线程才会继续执行。

使用场景
通常用于线程间协调,确保某些操作完成后才进行下一步工作。比如,可以等待多个线程任务完成后再进行合并操作。

示例代码

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 3;
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread finished");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            }).start();
        }
        
        latch.await();  // 主线程等待
        System.out.println("All threads finished");
    }
}

ConcurrentHashMap 和 Hashtable 的区别?

特性ConcurrentHashMapHashtable
线程安全分段锁机制整体锁
性能高效并发性能较差
空值支持支持空值不支持空值
迭代器弱一致迭代器强一致迭代器

6. 什么是线程池,Java如何实现线程池管理?

参考回答:
线程池是一个通过预先创建一定数量的线程来处理任务的机制,可以减少频繁创建和销毁线程的开销,提高程序的性能和资源利用率。Java通过java.util.concurrent包中的ExecutorService接口及其实现来提供线程池管理。

常用线程池实现

  • FixedThreadPool:创建一个固定大小的线程池,适用于任务数已知且固定的场景。

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

  • CachedThreadPool:创建一个可以根据需要创建新线程的线程池,适用于任务数不确定且可能较多的场景。

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

  • SingleThreadExecutor:创建一个单线程的线程池,适用于顺序执行的任务。

    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

  • ScheduledThreadPoolExecutor:支持任务定时执行的线程池。

    ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);

线程池的好处

  • 降低了线程创建和销毁的开销。

  • 可以通过合理配置线程池的大小来平衡性能和资源消耗。

  • 通过队列管理任务的执行顺序。


7. Java中如何处理并发中的线程安全问题?

参考回答:
Java提供了几种方式来处理线程安全问题:

  1. 使用volatile:确保变量的可见性,适用于单一变量的同步。

  2. 使用synchronized:保证同一时刻只有一个线程能够访问同步块或方法。

  3. 使用ReentrantLock:比synchronized提供更灵活的锁控制,包括定时锁、可中断锁等。

  4. 使用并发集合:例如ConcurrentHashMapCopyOnWriteArrayList等,适用于高并发环境下的数据结构。

  5. 使用原子变量:通过java.util.concurrent.atomic包中的类(如AtomicInteger)来保证操作的原子性。

8. 解释一下什么是“ABA问题”,并提供解决方案。

参考回答:
ABA问题:这是指一个线程在执行操作时检查变量的值是否为A,假设值没有变化,然后修改该值为B,再修改为A。此时其他线程可能也修改了该值,但并不会检测到这个变化。由于变量值最终回到A,线程并不知道该值已经被改变过,从而导致错误的结果。

解决方案

  1. 使用版本号:通过引入版本号来标记变量的变化。每次修改时都更新版本号,从而避免ABA问题。

  2. AtomicStampedReferencejava.util.concurrent.atomic.AtomicStampedReference类结合了原子操作和版本控制,避免了ABA问题。

    AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);
    int[] stamp = new int[1];
    Integer currentValue = ref.get(stamp);
    // Do something with currentValue
    ref.compareAndSet(currentValue, 200, stamp[0], stamp[0] + 1);
    

9. ExecutorServicesubmit()invokeAll()方法有什么区别?

参考回答:

  • submit()

    • 提交一个单独的任务给线程池,并返回一个Future对象,表示任务的执行结果。

    • 可以提交RunnableCallable任务。

    • 适用于你只关心一个任务的执行结果的情况。

ExecutorService executor = Executors.newFixedThreadPool(10);
Future<Integer> future = executor.submit(() -> {
    return 1 + 1;
});
// 阻塞直到任务完成
Integer result = future.get();  
  • invokeAll()

    • 提交一个Collection类型的任务给线程池,并返回一个List<Future>对象,表示每个任务的执行结果。

    • 所有任务会并发执行,并且invokeAll()会等到所有任务完成后返回。

    • 适用于你需要同时处理多个任务并等待所有任务完成的场景。

ExecutorService executor = Executors.newFixedThreadPool(10);
List<Callable<Integer>> tasks = Arrays.asList(
    () -> 1 + 1,
    () -> 2 + 2,
    () -> 3 + 3
);
List<Future<Integer>> futures = executor.invokeAll(tasks);
for (Future<Integer> future : futures) {
 // 输出每个任务的结果
    System.out.println(future.get()); 
}

10. 什么是ThreadLocal,它是如何工作的?

参考回答:
ThreadLocal是Java中的一个类,用于为每个线程提供一个独立的变量副本。每个线程都会持有该变量的一个副本,因此不同线程之间不会相互干扰,避免了线程安全问题。

工作原理

  • ThreadLocal变量在每个线程中都有一个独立的副本,因此多个线程同时访问该变量时,互不影响。

  • 每个线程通过ThreadLocal.get()获取当前线程的变量副本,使用ThreadLocal.set()设置当前线程的变量副本。

使用场景
适用于每个线程需要独立数据的情况,如数据库连接、会话信息等。

示例代码

public class ThreadLocalExample {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

    public static void main(String[] args) {
        System.out.println("Main thread value: " + threadLocal.get());
        
        Thread thread1 = new Thread(() -> {
            threadLocal.set(10);
            System.out.println("Thread 1 value: " + threadLocal.get());
        });
        
        Thread thread2 = new Thread(() -> {
            threadLocal.set(20);
            System.out.println("Thread 2 value: " + threadLocal.get());
        });
        
        thread1.start();
        thread2.start();
    }
}

注意事项

  • ThreadLocal变量通常会在线程结束时自动清理,但在一些高并发的场景中,如果使用不当(例如没有及时清除),可能会导致内存泄漏。


11. Java中如何实现线程间的通信?

参考回答:
Java中可以通过以下方式实现线程间的通信:

  1. 使用wait()notify()notifyAll()方法:这些方法是Object类的一部分,通过在同步代码块中使用wait()来让线程等待,notify()或者notifyAll()用来唤醒等待的线程。

    示例代码

    class SharedResource {
        private int count = 0;
        
        public synchronized void increment() throws InterruptedException {
            while (count >= 5) {
                wait();  // 等待条件满足
            }
            count++;
            System.out.println("Count: " + count);
            notify();  // 通知其他线程
        }
    
        public synchronized void decrement() throws InterruptedException {
            while (count <= 0) {
                wait();  // 等待条件满足
            }
            count--;
            System.out.println("Count: " + count);
            notify();  // 通知其他线程
        }
    }
    
    public class ThreadCommunicationExample {
        public static void main(String[] args) {
            SharedResource resource = new SharedResource();
            new Thread(() -> {
                try {
                    for (int i = 0; i < 6; i++) {
                        resource.increment();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
    
            new Thread(() -> {
                try {
                    for (int i = 0; i < 6; i++) {
                        resource.decrement();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
    
  2. 使用BlockingQueuejava.util.concurrent包中的BlockingQueue是一个线程安全的队列,提供了阻塞的插入和删除操作,可以有效地解决线程间的通信问题。

    示例代码

    class Producer implements Runnable {
        private BlockingQueue<Integer> queue;
        
        public Producer(BlockingQueue<Integer> queue) {
            this.queue = queue;
        }
    
        @Override
        public void run() {
            try {
                for (int i = 0; i < 5; i++) {
                    queue.put(i);  // 放入数据
                    System.out.println("Produced: " + i);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
    
    class Consumer implements Runnable {
        private BlockingQueue<Integer> queue;
        
        public Consumer(BlockingQueue<Integer> queue) {
            this.queue = queue;
        }
    
        @Override
        public void run() {
            try {
                for (int i = 0; i < 5; i++) {
                    Integer value = queue.take();  // 获取数据
                    System.out.println("Consumed: " + value);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
    
    public class BlockingQueueExample {
        public static void main(String[] args) {
            BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
            new Thread(new Producer(queue)).start();
            new Thread(new Consumer(queue)).start();
        }
    }
    

12. FutureCallable的区别是什么?

参考回答:

  • Callable:是一个函数式接口,类似于Runnable,但是它的call()方法可以返回一个值,并且能够抛出异常。适用于需要计算结果的任务。

  • Future:表示一个异步计算的结果。submit()方法会返回一个Future对象,允许你查询任务的执行状态,或者获取任务的结果。通过Future.get()方法可以阻塞当前线程直到任务完成,并返回任务的结果。

区别

  • Callable可以返回计算结果,Runnable不能。

  • Future用于获取异步计算的结果,而Callable是定义计算逻辑的任务。

13. ConcurrentHashMap 和 Hashtable 的区别?

参考回答:

特性ConcurrentHashMapHashtable
线程安全分段锁机制整体锁
性能高效并发性能较差
空值支持支持空值不支持空值
迭代器弱一致迭代器强一致迭代器

14. Java 中的线程池有哪些实现?

参考回答:
Java 提供了以下几种线程池实现:

  1. Executors.newFixedThreadPool(int nThreads):固定大小线程池。
  2. Executors.newSingleThreadExecutor():单线程池。
  3. Executors.newCachedThreadPool():缓存线程池。
  4. Executors.newScheduledThreadPool(int corePoolSize):支持延迟任务的线程池。

15. 什么是线程池的工作原理?

参考回答:
线程池的工作原理如下:

  1. 任务提交:任务被提交到队列中。
  2. 线程复用:线程池中的线程从队列中获取任务并执行。
  3. 线程回收:线程空闲时会被回收以节省资源。
  4. 动态调整:根据负载动态调整线程数量。

16. Java 中的线程安全集合有哪些?

参考回答:
Java 提供了以下线程安全集合:

  1. ConcurrentHashMap
  2. CopyOnWriteArrayList
  3. CopyOnWriteArraySet
  4. ConcurrentLinkedQueue
  5. BlockingQueue(如 LinkedBlockingQueue

💡思考题参考解答

参考地址:打印零与奇偶数

为了实现多线程之间的同步和协作,确保输出顺序符合要求,我们会使用 Java 的 synchronized 关键字来保证线程安全,利用 wait() 和 notifyAll() 方法来控制线程的执行顺序。同时,借助一个布尔类型的标记变量 isZero 来决定当前应该打印 0 还是奇数 / 偶数。

class ZeroEvenOdd {
    private final int n;
    private int x = 1;
    // true: 打印0  false: 不打印0
    private boolean isZero = true;

    public ZeroEvenOdd(int n) {
        this.n = n;
    }

    public void zero(IntConsumer printNumber) throws InterruptedException {
        while (x <= n) {
            synchronized (this) {
                if (isZero) {
                    printNumber.accept(0);
                    isZero = false;
                    this.notifyAll();
                } else {
                    this.wait();
                }
            }
        }
    }

    public void even(IntConsumer printNumber) throws InterruptedException {
        while (x <= n) {
            synchronized (this) {
                if (x % 2 == 0 && !isZero) {
                    printNumber.accept(x);
                    isZero = true;
                    x++;
                    this.notifyAll();
                } else {
                    this.wait();
                }
            }
        }
    }

    public void odd(IntConsumer printNumber) throws InterruptedException {
        while (x <= n) {
            synchronized (this) {
                if (x % 2 != 0 && !isZero) {
                    printNumber.accept(x);
                    isZero = true;
                    x++;
                    this.notifyAll();
                } else {
                    this.wait();
                }
            }
        }
    }
}

解答内容:

作者:晨晨
链接:https://leetcode.cn/problems/print-zero-even-odd/solutions/3070573/shi-yong-synchronizedwaitfang-fa-notifya-vatc/
来源:力扣(LeetCode)著作权归作者所有。

至此,还有补充的,欢迎👏评论留言🎉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值