面试题:三个线程按顺序打印 ABCABC

news2024/11/15 8:23:27

小伙伴们好呀,最近在重新复习,整理自己的知识库,偶然看到这道面试题:三个线程按顺序打印 ABCABC,尝试着做一下,才发现自己对线程还有好多地方不懂,蓝瘦…… 🐷

思路

很明显,这里就涉及线程间相互通信的知识了。

而相互通信的难点就是要控制好,阻塞和唤醒的时机。

一. 这里就是 A 通知 B,B 通知 C , C 通知 A

 

二. 三个线程在等待(阻塞)和唤醒(执行) 中不断切换。

三. 等待的方式大致分为两种

  • wait 方法  (Object native 方式 )

  • LockSupport.park 方式 ( Unsafe native 方式 )

四. 唤醒的方式

  • notify,notifyAll 方法  (Object native 方式 )

  • LockSupport.unPark 方式 ( Unsafe native 方式 )

五. 互斥条件

线程 A 先拿到资源 c,再拿资源 a ,[a 执行完后释放,并唤醒等待资源 a]  的 线程 B 线程 B 先拿到资源 a,再拿资源 b ,[b 执行完后释放,并唤醒等待资源 b]  的 线程 C 线程 C 先拿到资源 b,再拿资源 c ,[c 执行完后释放,并唤醒等待资源 c]  的 线程 A

所以得有 三个 共享资源 abc 来达到互斥条件

Synchronized 还是 ReentrantLock 都得建立 三个共享资源

六. 扩展 

使用 LockSupport ,如果要像上面这样子的思路去解答,就得注意 线程相互引用行成的循环依赖问题,这里借用 Spring 的思路 用 Map 巧妙化解。 

或者做法2 通过 外部的成员变量,不断地去判断,unpark 线程 a b c

Synchronized 方式

private static class MySynchronized {

    void printABC() throws InterruptedException {

        class MyRunable implements Runnable {

            private Object lock1;
            private Object lock2;
            private CountDownLatch countDownLatch;

            public MyRunable(Object lock1, Object lock2) {
                this.lock1 = lock1;
                this.lock2 = lock2;
            }

            public MyRunable(Object lock1, Object lock2, CountDownLatch countDownLatch) {
                this.lock1 = lock1;
                this.lock2 = lock2;
                this.countDownLatch = countDownLatch;
            }

            @Override
            public void run() {
                boolean running = false;

                int count = 2;
                while (count > 0) {
                    // C,A - > A  唤醒 B 线程
                    // A,B - > B  唤醒 C 线程
                    // B,C - > C  唤醒 A 线程 (最后一次执行时,唤醒 A 后,A 发现 count =0,就不执行了。
                    synchronized (lock1) {

                        synchronized (lock2) {
                            System.out.println(Thread.currentThread().getName());
                            count--;
                            // lock2 方法块执行结束前,唤醒其他线程。
                            lock2.notify();
                        }
                        // 线程执行完毕后
                        if (countDownLatch != null && !running) {
                            countDownLatch.countDown();
                            running = true;
                        }

                        try {
                            // 释放锁
                            lock1.wait();
                        } catch (InterruptedException e) {
                        }

                    }

                }
                System.out.println(Thread.currentThread().getName() + " over");
                synchronized (lock2) {
                    // 唤醒其他线程。
                    lock2.notify();
                }
            }
        }

        CountDownLatch countDownLatch = new CountDownLatch(1);
        CountDownLatch countDownLatch2 = new CountDownLatch(1);

        Object a = new Object();
        Object b = new Object();
        Object c = new Object();

        MyRunable ra = new MyRunable(c, a, countDownLatch);
        MyRunable rb = new MyRunable(a, b, countDownLatch2);
        MyRunable rc = new MyRunable(b, c);


        Thread a1 = new Thread(ra, "A");
        a1.start();

        countDownLatch.await();

        Thread b1 = new Thread(rb, "B");
        b1.start();

        countDownLatch2.await();

        Thread c1 = new Thread(rc, "C");
        c1.start();


    }
}

这里我借用 countDownLatch 去控制线程的启动流程,尽量不使用 Thread.sleep() 来实现,拿捏线程的执行,通信步骤。

写这个的时候,除了一开始思路不清晰外,还出现一个小状况,就是 程序执行完卡住了。

 

debug 发现线程 B C 还在 wait 状态,这是写时候容易疏忽的。

要记得在循环外再次唤醒其他线程,让他们走完方法。

ReentrantLock 方式

private static class MyReentrantLock {

    int number = 6;

    void printABC() {
        ReentrantLock lock = new ReentrantLock();

        Condition conditionA = lock.newCondition();
        Condition conditionB = lock.newCondition();
        Condition conditionC = lock.newCondition();


        class MyRunnable implements Runnable {

            ReentrantLock lock;
            Condition condition1;
            Condition condition2;


            public MyRunnable(ReentrantLock lock, Condition condition1, Condition condition2) {
                this.lock = lock;
                this.condition1 = condition1;
                this.condition2 = condition2;
            }

            @Override
            public void run() {
                int count = 2;
                while (count > 0) {
                    lock.lock();
                    try {
                        String name = Thread.currentThread().getName();

                        if (
                                number % 3 != 0 && "A".equals(name)
                                        || number % 3 != 2 && "B".equals(name)
                                        || number % 3 != 1 && "C".equals(name)
                        ) {
                            condition1.await();
                        }
                        System.out.println(name + " : " + number);
                        number--;
                        count--;
                        condition2.signal();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();

                    }
                }

            }
        }


        new Thread(new MyRunnable(lock, conditionC, conditionA), "A").start();

        new Thread(new MyRunnable(lock, conditionA, conditionB), "B").start();

        new Thread(new MyRunnable(lock, conditionB, conditionC), "C").start();

    }
}

Synchronized 会了之后,这个也很简单了。

就是上锁的地方换成 lock.lock();,把三个共享资源换成 lock.newCondition();

然后思考一下阻塞条件 condition1.await() 。

毕竟 打印 和 唤醒 的操作总是在一起的。

 

Semaphore 我也写了,但是感觉不太适合,毕竟它的作用是用来控制并发线程数的,我直接创建三个 Semaphore  总觉得怪怪的。🐖

LockSupport 方式

这里我写了两种方法

   private static class MyLockSupport {
        volatile int number = 6;

        void printABC() throws InterruptedException {
            class MyRunnable implements Runnable {

                @Override
                public void run() {
                    int count = 2;
                    while (count > 0) {
                        LockSupport.park(this);
                        System.out.println(Thread.currentThread().getName());
                        count--;
                    }
                }
            }
            Thread a = new Thread(new MyRunnable(), "A");
            Thread b = new Thread(new MyRunnable(), "B");
            Thread c = new Thread(new MyRunnable(), "C");

            a.start();
            b.start();
            c.start();


            while (number > 0) {
                if (number % 3 == 0) {
                    LockSupport.unpark(a);
                } else if (number % 3 == 2) {
                    LockSupport.unpark(b);
                } else {
                    LockSupport.unpark(c);
                }
                number--;
                LockSupport.parkNanos(this, 200 * 1000);
//                LockSupport.parkUntil(this,System.currentTimeMillis()+3000L);
            }

        }

       // 用 map 解决线程循环依赖的问题
        void printABC2() throws InterruptedException {

            class MyRunnable implements Runnable {

                Map<String, Thread> map;

                public MyRunnable(Map<String, Thread> map) {
                    this.map = map;
                }

                @Override
                public void run() {
                    int count = 2;

                    String name = Thread.currentThread().getName();
                    String key = "A".equals(name) ? "B" : "B".equals(name) ? "C" : "A";

                    while (count > 0) {
                        if (
                                number % 3 == 0 && "A".equals(name)
                                        || number % 3 == 2 && "B".equals(name)
                                        || number % 3 == 1 && "C".equals(name)
                        ) {

                            System.out.println(name);
                            count--;
                            number--;
                            LockSupport.unpark(map.get(key));
                        }
                        LockSupport.park(this);
                    }

                    LockSupport.unpark(map.get(key));

                }

            }

            Map<String, Thread> map = new HashMap<>();


            Thread a = new Thread(new MyRunnable(map), "A");
            Thread b = new Thread(new MyRunnable(map), "B");
            Thread c = new Thread(new MyRunnable(map), "C");

            map.put("A", a);
            map.put("B", b);
            map.put("C", c);

            a.start();
            b.start();
            c.start();


        }
    }

LockSupport 我也是第一次用,它使用起来也很方便,就单纯的 阻塞和唤醒线程 ,对应 park 和 unPark 方法。

它不要求你像 wait 那样子,必须写在 Synchronized 代码块里,被 Monitor 监视才行。

但同时,也意味着你必须控制好这个 锁的范围 。

你可以自由阻塞代码,在具备某个条件时,唤醒特定的线程,让它继续执行。

实际上,上面 ReentrantLock 中的 Condition await 方法,底层就是调用 LockSupport 的 park 方法。

这也是我开头说的通信大致分为两种方式的原因。

方法一中,我是用 parkNanos 阻塞一段时间,然后就继续运行,也算是取巧不用 Thread.Sleep 了吧😝

方法二 我比较喜欢,思路也是同开头两种,打印完唤醒其他线程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/71386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker可视化工具Portainer安装

一、官网介绍 官方地址&#xff1a;https://www.portainer.io/ 致力于为开发者做最强大的docker管理平台二、在 Linux 上使用 Docker 安装 Portainer 官方文档地址&#xff1a;https://docs.portainer.io/start/install/server/docker/linux 1、部署前需知 &#xff08;1&a…

SautinSoft JBIG2 .Net提供了解读jb2文档的API

SautinSoft JBIG2 .Net提供了解读jb2文档的API SautinSoft的JBIG2.Net是一个独立且简单的SDK&#xff0c;为您提供了解读jb2文档的API。该部分将使您的软件能够使用3-4个C#行将JBIG2文件的任何网页转换为照片格式&#xff1a;png、Tiff、jpeg。 JBIG2.Net能给我什么 节省项目开…

PyQt中的多线程QThread示例

PyQt中的多线程一、PyQt中的多线程二、创建线程2.1 设计ui界面2.2 设计工作线程2.3 主程序设计三、运行结果示例一、PyQt中的多线程 传统的图形用户界面应用程序都只有一个执行线程&#xff0c;并且一次只执行一个操作。如果用户从用户界面中调用一个比较耗时的操作&#xff0…

JAVA SCRIPT设计模式--结构型--设计模式之Proxy代理模式(12)

JAVA SCRIPT设计模式是本人根据GOF的设计模式写的博客记录。使用JAVA SCRIPT语言来实现主体功能&#xff0c;所以不可能像C&#xff0c;JAVA等面向对象语言一样严谨&#xff0c;大部分程序都附上了JAVA SCRIPT代码&#xff0c;代码只是实现了设计模式的主体功能&#xff0c;不代…

搭建LNMP平台实现负载均衡与高可用

目录 环境要求 安装服务 搭建服务 1. nfs主机操作 2. web1主机操作 3. web2主机操作 4. php主机操作 5. mysql主机操作 6. 验证1 7. lb1主机操作 8. lb2主机操作 9. 验证2 测试1 测试2 网站故障解决 验证 环境要求 实验目标&#xff1a;搭建LNMP平台实现负载均衡与高可用。 拓…

web课程设计——健身俱乐部健身器材网站模板(24页)HTML+CSS+JavaScript

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

解析Vue项目每一个文件夹及文件的作用

使用vue-cli2.x脚手架为每个vue项目创建脚手架项目结构&#xff0c;项目结构目录中每个文件夹介绍如下&#xff1a; 1、build 文件夹 里面是对 webpack 开发和打包的相关设置&#xff0c;包括入口文件&#xff0c;输出文件&#xff0c;使用的模块等。 1.1 webpack.base.conf.j…

人大金仓 +用友:助力企业数智化转型,实现高质量发展

金兰组织联合解决方案集数字化浪潮下&#xff0c;联合解决方案已经成为这个时代的必然选择。如何依托各自产品的功能与特点&#xff0c;持续优化和完善解决方案能力&#xff0c;满足客户更多、更复杂的业务诉求&#xff0c;成为数字服务厂商的重要工作内容。为此&#xff0c;金…

我的电脑图标没了怎么办?3个方法找回消失的图标

最近&#xff0c;很多小伙伴都在私信小编&#xff0c;说他们的电脑桌面图标不见了。我的电脑图标没了怎么办&#xff1f;别担心。图标突然消失了&#xff0c;很可能是你不小心隐藏了桌面图标。这里有3个方法可以帮助你快速恢复并找到电脑图标&#xff0c;一起来看看吧&#xff…

现代基准测试程序种类以及使用方法

文章目录基准测试程序种类常见测试程序概述常见测试程序使用Dhrystone的使用UnixBench的使用CPU-Z的安装与使用参考文献现代计算机的性能测量极大地依赖于在其上运行的工作负载&#xff0c;为了测量和分析计算机系统的性能人们常常选择或构造一组能反映其工作负载特征的程序&am…

python文件转换成exe可执行的windows文件

一、介绍 python的程序需要运行环境有时候很不方便&#xff0c;因为要交给别人代码可能因为环境的原因运行各种问题&#xff0c;这里给出直接讲python程序转换成exe文件&#xff0c;很方便直接给执行程序就可以&#xff0c;也不用担心运行环境问题而运行不了 二、工具 1、安装…

[奶奶看了都会]ChatGPT保姆级注册教程

大家好&#xff0c;我是小卷 最近几天OpenAI发布的ChatGPT聊天机器人火出天际了&#xff0c;连着上了各个平台的热搜榜。这个聊天机器人最大的特点是模仿人类说话风格同时回答大量问题。 有人说ChatGPT是真正的人工智能&#xff0c;它不仅能和你聊天&#xff0c;还是写小作文…

[附源码]Python计算机毕业设计SSM基于微信平台的匿名电子投票系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

web课程设计网页规划与设计 HTML+CSS+JavaScript仿英雄联盟LOL首页(1个页面)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

第4季4:图像sensor的驱动源码解析

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、sensor驱动源码的框架 mpp定义了一整套sensor驱动的实现和封装&#xff0c;这里以ar0130型号的sensor为例进行说明。 1、sensor层驱动 &#xff08;1&#xff09;sensor层驱动位于mpp/componen…

Java基础面试题

请介绍全局变量和局部变量的区别 Java中的变量分为成员变量和局部变量&#xff0c;它们的区别如下&#xff1a; 成员变量&#xff1a; 成员变量是在类的范围里定义的变量&#xff1b;成员变量有默认初始值&#xff1b;未被static修饰的成员变量也叫实例变量&#xff0c;它存储于…

知识图谱-KGE-语义匹配-双线性模型-2019:TuckER

【paper】 TuckER: Tensor Factorization for Knowledge Graph Completion【简介】 这篇文章是英国爱丁堡大学的研究者发表于 ICML 2019 上的文章&#xff0c;提出了 TuckER&#xff0c;是一个线性的张量分解模型&#xff0c;对表示三元组事实的二值张量做 Tucker 分解。 背景…

python中nmupy获取本地数据和索引

1. numpy读取数据 可以使用numpy中的loadtxt进行数据读取&#xff0c;所包含的参数如下 参数名解释frame文件&#xff0c;字符串等也可以是.gz或bz2压缩文件dtype数据类型&#xff0c;即CSV中字符串以什么数据类型读入数组中&#xff0c;默认是np.floatdelimiter分隔字符串&a…

CAS:2379387-10-5;TPE-丙烯酰胺;AIE聚集诱导发光

英文名称:2-Propenamide,N-[4-(1,2,2-triphenylethenyl)phenyl]- 英文同义词:2-Propenamide,N-[4-(1,2,2-triphenylethenyl)phenyl]- CAS号:2379387-10-5 分子式:C29H23NO 分子量:401.5 结构式&#xff1a; AIE聚集诱导发光材料的特点&#xff1a; 1.在固态下有强发光特性&…

Typecho-handsome主题如何统计全站字数

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…