高频面试题:如何分别用三种姿势实现三个线程交替打印0到100

简介: 高频面试题:如何分别用三种姿势实现三个线程交替打印0到100

最近面试遇到的一道题,需要三个线程交替打印0-100,当时对多线程并不是很熟悉因此没怎么写出来,网上搜了之后得到现

synchronized + wait/notifyAll

实现思路:判断当前打印数字和线程数的取余,不等于当前线程则处于等待状态。循环结束唤醒所有等待线程。

public class PrintExample {
    //创建一个公共锁对象
    private static final Object Lock = new Object();
    //执行线程数
    private static final int THREAD_COUNT = 3;
    //打印数字的起始点
    private static volatile int START = 0;
    //打印数字的结束点
    private static final int END = 100;
    private static class Print implements Runnable{
        private final int index;
        public Print(int index){
            this.index = index;
        }
        @Override
        public void run() {
            while(START<END){
                synchronized (Lock){
                    //START和线程数进行取余,如果不等于当前线程的则等待
                    while(START % THREAD_COUNT != index){
                        try{
                            Lock.wait();
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                    //否则进行输出
                    if(START<=END){
                        System.out.println("Thread" + (index+1) +  ",打印结果:" + START);
                    }
                    START++;
                    //唤醒等待线程
                    Lock.notifyAll();
                }
            }
        }
        public static void main(String[] args) {
            for(int i = 0; i < THREAD_COUNT; i++){
                new Thread(new Print(i)).start();
            }
        }
    }
}

 

 

 

ReetrantLock + await/signalAll

实现思路:实现方式和synchronized + wait/notifyAll儿乎完全一样。我们只需要4步:

1.synchronized 替换为ReentrantLock

2.根据锁对象创建一个Condition对象

3.wait替换成await

4.notifyAll 替换为 signalAll

 

public class PrintExample {
    //创建一个公共锁对象
    private static final ReentrantLock Lock = new ReentrantLock();
    //根据锁对象创建一个Condition对象
    private static final Condition CONDITION = Lock.newCondition();
    //执行线程数
    private static final int THREAD_COUNT = 3;
    //打印数字的起始点
    private static volatile int START = 0;
    //打印数字的结束点
    private static final int END = 100;
    private static class Print implements Runnable{
        private final int index;
        public Print(int index){
            this.index = index;
        }
        @Override
        public void run() {
            while(START<END){
                Lock.lock();
                try {
                    //START和线程数进行取余,如果不等于当前线程的则等待
                    while(START % THREAD_COUNT != index){
                        try{
                            CONDITION.await();
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                    //否则进行输出
                    if(START<=END){
                        System.out.println("Thread" + (index+1) +  ",打印结果:" + START);
                    }
                    START++;
                    //唤醒等待线程
                    CONDITION.signalAll();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    Lock.unlock();
                }
                }
            }
        public static void main(String[] args) {
            for(int i = 0; i < THREAD_COUNT; i++){
                new Thread(new Print(i)).start();
            }
        }
    }
}

 

ReetrantLock + await/signal

因为Condition相对wait/notify方式,可以唤醒指定线程。那我们就完全不用每次都唤醒全部线程,仅需要唤醒下一次需要执行的线程就可以了。

相比较 ReentrantLock + await/signalAll 改进方法:

1.去除公共的Condition对象,替换为List<Condition> conditions;

2.调用"下一个线程的"Condition对象的signal方法唤醒下一个线程;

public class PrintExample {
    //创建一个公共锁对象
    private static final ReentrantLock Lock = new ReentrantLock();
    //根据锁对象创建一个Condition对象
    //private static final Condition CONDITION = Lock.newCondition();
    //执行线程数
    private static final int THREAD_COUNT = 3;
    //打印数字的起始点
    private static volatile int START = 0;
    //打印数字的结束点
    private static final int END = 100;
    private static class Print implements Runnable{
        private final int index;
        private final List<Condition> conditions;
        public Print(int index,List<Condition> conditions){
            this.index = index;
            this.conditions = conditions;
        }
        //只唤醒下一个线程
        private void signalNext(){
            int nextIndex = (index + 1) % THREAD_COUNT;
            conditions.get(nextIndex).signal();
        }
        @Override
        public void run() {
            while(START<END){
                Lock.lock();
                try {
                    //START和线程数进行取余,如果不等于当前线程的则等待
                    while(START % THREAD_COUNT != index){
                        try{
                            conditions.get(index).await();
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                    //否则进行输出
                    if(START<=END){
                        System.out.println("Thread" + (index+1) +  ",打印结果:" + START);
                    }
                    START++;
                    //唤醒等待线程
                    signalNext();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    Lock.unlock();
                }
                }
            }
        public static void main(String[] args) {
            List<Condition> conditionList = new ArrayList<>();
            conditionList.add(Lock.newCondition());
            conditionList.add(Lock.newCondition());
            conditionList.add(Lock.newCondition());
            for(int i = 0; i < THREAD_COUNT; i++){
                new Thread(new Print(i,conditionList)).start();
            }
        }
    }
}

此处使用 List<Condition> conditions让每个线程都拥有属于自己的condition,这样可以单独唤醒和等待。

Condition是什么

概念:

condition可以理解为条件队列。当一个线程在调用了其await方法以后,直到线程等待的某个条件为真的时候才会被唤醒。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现

方法:

Condition依赖于Lock接口

方法 解释
lock.newCondition() 生成一个Condition
await() 对应Object的wait();使线程等待
signal() 对应Object的notify();唤醒线程

注意:调用Condition的await()和signal()方法,都必须在lock.lock()和lock.unlock()之间使用

在生产者和消费者中Condition的执行方式:

  • 当在线程Consumer中调用await方法后,线程Consumer将释放锁,并且将自己沉睡,等待唤醒。
  • 这时等到线程Producer获取到锁后,开始执行任务,完毕后,调用Condition的signalall方法,唤醒线程Consumer,线程Consumer恢复执行。

以上说明Condition是一个多线程间协调通信的工具类,使得某个或某些线程一起等待某个条件(Condition),只有当该条件具备( signal 或者 signalAll方法被带调用)时 ,这些等待线程才会被唤醒,从而重新争夺锁

 

 

目录
相关文章
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
74 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
2月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
51 2
面试官:说说停止线程池的执行流程?
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:如何实现线程池任务编排?
面试官:如何实现线程池任务编排?
33 1
面试官:如何实现线程池任务编排?
|
3月前
|
Java
【多线程面试题二十五】、说说你对AQS的理解
这篇文章阐述了对Java中的AbstractQueuedSynchronizer(AQS)的理解,AQS是一个用于构建锁和其他同步组件的框架,它通过维护同步状态和FIFO等待队列,以及线程的阻塞与唤醒机制,来实现同步器的高效管理,并且可以通过实现特定的方法来自定义同步组件的行为。
【多线程面试题二十五】、说说你对AQS的理解
|
3月前
|
Java
【多线程面试题十六】、谈谈ReentrantLock的实现原理
这篇文章解释了`ReentrantLock`的实现原理,它基于Java中的`AbstractQueuedSynchronizer`(AQS)构建,通过重写AQS的`tryAcquire`和`tryRelease`方法来实现锁的获取与释放,并详细描述了AQS内部的同步队列和条件队列以及独占模式的工作原理。
【多线程面试题十六】、谈谈ReentrantLock的实现原理
|
3月前
|
消息中间件 缓存 算法
Java多线程面试题总结(上)
进程和线程是操作系统管理程序执行的基本单位,二者有明显区别: 1. **定义与基本单位**:进程是资源分配的基本单位,拥有独立的内存空间;线程是调度和执行的基本单位,共享所属进程的资源。 2. **独立性与资源共享**:进程间相互独立,通信需显式机制;线程共享进程资源,通信更直接快捷。 3. **管理与调度**:进程管理复杂,线程管理更灵活。 4. **并发与并行**:进程并发执行,提高资源利用率;线程不仅并发还能并行执行,提升执行效率。 5. **健壮性**:进程更健壮,一个进程崩溃不影响其他进程;线程崩溃可能导致整个进程崩溃。
47 2
|
3月前
|
存储 安全 容器
【多线程面试题二十一】、 分段锁是怎么实现的?
这篇文章解释了分段锁的概念和实现方式,通过将数据分成多个段并在每段数据上使用独立锁,从而降低锁竞争,提高并发访问效率,举例说明了`ConcurrentHashMap`如何使用分段锁技术来实现高并发和线程安全。
【多线程面试题二十一】、 分段锁是怎么实现的?
|
3月前
|
安全 Java
【多线程面试题十九】、 公平锁与非公平锁是怎么实现的?
这篇文章解释了Java中`ReentrantLock`的公平锁和非公平锁的实现原理,其中公平锁通过检查等待队列严格按顺序获取锁,而非公平锁允许新线程有更高机会立即获取锁,两者都依赖于`AbstractQueuedSynchronizer`(AQS)和`volatile`关键字以及CAS技术来确保线程安全和锁的正确同步。
【多线程面试题十九】、 公平锁与非公平锁是怎么实现的?
|
3月前
|
存储 缓存 安全
Java多线程面试题总结(中)
Java内存模型(JMM)定义了程序中所有变量的访问规则与范围,确保多线程环境下的数据一致性。JMM包含主内存与工作内存的概念,通过8种操作管理两者间的交互,确保原子性、可见性和有序性。`synchronized`和`volatile`关键字提供同步机制,前者确保互斥访问,后者保证变量更新的可见性。多线程操作涉及不同状态,如新建(NEW)、可运行(RUNNABLE)等,并可通过中断、等待和通知等机制协调线程活动。`volatile`虽不确保线程安全,但能确保变量更新对所有线程可见。
19 0

相关实验场景

更多