• 面试题 | 记录面试时碰到的一道多线程题目


    记录笔试时碰到的一个多线程题目…
    做题时的感觉:
    1.很多线程方法都忘了,而且对某些关键字的理解会有些刻板印象
    2.现在的想法是…看看能不能通过这道题,练习利用idea来进行多线程debug
    3.归纳易混淆的和线程生命周期有关的方法(记得当时记在了微信的对话框上)
    4.新的内容:阻塞队列及应用,CountDownLatch是基于阻塞队列的应用
    当前进度:看了一些多线程打印题,但是不能复写

    题目
    package com.exampledb.demo.po;
    
    import lombok.SneakyThrows;
    
    public class ThreadTest {
        @SneakyThrows
        public static void main(String[] args) {
            StringBuilder stringBuilder = new StringBuilder();
            Thread t1 = new Thread(() -> {
                synchronized (stringBuilder) {
                    stringBuilder.append("A");
                    try {
                        stringBuilder.wait();
                    } catch (InterruptedException e) {
                        System.out.println("hhhhhhhh");
                    }
                    stringBuilder.append("B");
                }
            });
    
            Thread t2 = new Thread(() -> {
                synchronized (stringBuilder) {
                    stringBuilder.append("C");
                    stringBuilder.notify();
                    stringBuilder.append("D");
                }
            });
    
            t1.start();
            Thread.sleep(300);
            t2.start();
    
            t1.join();
            t2.join();
            System.out.println(stringBuilder.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    写题时的疑惑
    1. join方法是让调用者插队吗?如果A线程先调join,B线程跟着调join,那么B会等A先执行完?还是会先越过B先执行?
    2. sync一定能保证代码块的原子性嘛
    3. notify和wait的作用
    4. wait是阻塞哪个线程,调用它的线程还是阻塞当前正在运行的线程?它要等别的线程调notify唤醒??
    运行结果

    ACDB

    参考资料

    Java 并发编程:线程间的协作(waittifyeep/yield/join)
    Java线程之sleep(), wait(), yield() 三个方法的作用和特点

    wait()、notify()、notifyAll()与sync,monitor、IllegalMonitorStateException异常

    wait方法的使用必须在同步的范围内,否则就会抛出IllegalMonitorStateException异常,wait方法的作用就是阻塞当前线程等待notify/notifyAll方法的唤醒,或等待超时后自动唤醒

    在这里插入图片描述
    wait和notify、notifyAll方法要搭配sync一起用。。不然会爆异常。。。
    这些方法的调用者持有的锁一定要和sync锁住的内容一致,不然也会报异常
    在这里插入图片描述

    笔记

    关键:是否会让出锁,是否会让出CPU资源。。还有sync是否能保持原子性,在于线程有没有调用方法把锁放掉,如果没放掉锁,那么就可以保持代码块的原子性
    在这里插入图片描述

    资料

    发现了一个并发专栏。。
    并发专栏
    寒食君

    CAS与sync

    CAS(Compare-and-Swap,即比较并替换)算法

    适用场景不同:
    CAS与synchronized的对比
    简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),
    synchronized适用于写比较多的情况下(多写场景,冲突一般较多)

    一道题

    阻塞队列

    在这里插入图片描述

    阻塞队列

    阻塞队列:
    多线程环境下控制数据的生产和消费端的数据处理效率匹配。。
    比如,生产者有多个线程生产数据,这些线程都放在阻塞队列A里;
    消费者有多个线程消费数据,这些线程都放在阻塞队列B里;

    当A生产数据太多,B消费不过来时,A里面的线程将会被阻塞,被挂起

    阻塞队列demo
    代码copy自原文

    生产者线程

    import java.util.Random;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 生产者线程
     *
     * @author jackyuj
     */
    public class Producer implements Runnable {
    
        private volatile boolean  isRunning = true;//是否在运行标志
        private BlockingQueue queue;//阻塞队列
        private static AtomicInteger count = new AtomicInteger();//自动更新的值
        private static final int DEFAULT_RANGE_FOR_SLEEP = 1000;
    
        //构造函数
        public Producer(BlockingQueue queue) {
            this.queue = queue;
        }
    
        public void run() {
            String data = null;
            Random r = new Random();
    
            System.out.println("启动生产者线程!");
            try {
                while (isRunning) {
                    System.out.println("正在生产数据...");
                    Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));//取0~DEFAULT_RANGE_FOR_SLEEP值的一个随机数
    
                    data = "data:" + count.incrementAndGet();//以原子方式将count当前值加1
                    System.out.println("将数据:" + data + "放入队列...");
                    if (!queue.offer(data, 2, TimeUnit.SECONDS)) {//设定的等待时间为2s,如果超过2s还没加进去返回true
                        System.out.println("放入数据失败:" + data);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            } finally {
                System.out.println("退出生产者线程!");
            }
        }
    
        public void stop() {
            isRunning = false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    消费者

    import java.util.Random;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 消费者线程
     *
     * @author jackyuj
     */
    public class Consumer implements Runnable {
    
        private BlockingQueue<String> queue;
        private static final int DEFAULT_RANGE_FOR_SLEEP = 1000;
    
        //构造函数
        public Consumer(BlockingQueue<String> queue) {
            this.queue = queue;
        }
    
        public void run() {
            System.out.println("启动消费者线程!");
            Random r = new Random();
            boolean isRunning = true;
            try {
                while (isRunning) {
                    System.out.println("正从队列获取数据...");
                    String data = queue.poll(2, TimeUnit.SECONDS);//有数据时直接从队列的队首取走,无数据时阻塞,在2s内有数据,取走,超过2s还没数据,返回失败
                    if (null != data) {
                        System.out.println("拿到数据:" + data);
                        System.out.println("正在消费数据:" + data);
                        Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));
                    } else {
                        // 超过2s还没数据,认为所有生产线程都已经退出,自动退出消费线程。
                        isRunning = false;
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            } finally {
                System.out.println("退出消费者线程!");
            }
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    测试类

    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.LinkedBlockingQueue;
    
    public class BlockingQueueTest {
    
        public static void main(String[] args) throws InterruptedException {
            // 声明一个容量为10的缓存队列
            BlockingQueue<String> queue = new LinkedBlockingQueue<String>(10);
    
            //new了三个生产者和一个消费者
            Producer producer1 = new Producer(queue);
            Producer producer2 = new Producer(queue);
            Producer producer3 = new Producer(queue);
            Consumer consumer = new Consumer(queue);
    
            // 借助Executors
            ExecutorService service = Executors.newCachedThreadPool();
            // 启动线程
            service.execute(producer1);
            service.execute(producer2);
            service.execute(producer3);
            service.execute(consumer);
    
            // 执行10s
            Thread.sleep(10 * 1000);
            producer1.stop();
            producer2.stop();
            producer3.stop();
    
            Thread.sleep(2000);
            // 退出Executor
            service.shutdown();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    并发社区文章

    并发章节

    多线程顺序打印总结

    多线程顺序打印题


    新的疑惑

    • 制造死锁,观察死锁导致的CPU飙高
    • 制造优先级
    • 各种方法的操作主体对象是谁。究竟是谁调用谁就让出?
      还是无论是谁主动调这个方法,被作用的对象总是当前在跑的那个线程??
    • yield和notify打配合?
    • yield会让出锁?但sleep不会,所以用sleep记得设置超时时间
    • jvm对线程的调度是无序的!这一点超级重要,开发者是无法百分百控制优先级的。
      因为会出现这样的情况,尽管某线程已经主动将资源让出,但是由于jvm对线程的调度是无序的,
      那么就会出现第二次jvm还是选中该线程继续执行的情况!!
    • 如果要加锁,那么锁的粒度越小越好,不要影响并发!!
    • 怎么往线程里传入参数?(构造方法,set方法,回调函数)回调方法体现的是解耦思想
    • 回调函数分为:同步回调,异步回调
  • 相关阅读:
    入门必读:Python try except异常处理详解
    安装Keras,tensorflow,并将虚拟环境添加到jupyter notebook
    家具行业APS解决方案
    【C++基础】2. 标准库
    backtrace输出任意线程栈信息
    【ubuntu】本地访问ubuntu服务器的jupyter文件,.ipynb误删除找回、恢复
    这款吊打Chrome、Edge的浏览器,时隔573天再度更新
    车载高速CAN(HighSpeed CAN)通信之CAN Bus Off
    C#可视化 家用轿车信息查询系统(具体做法及全部代码)
    【押题】24考研押题
  • 原文地址:https://blog.csdn.net/xianyu_x/article/details/122281575