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

记录笔试时碰到的一个多线程题目…
做题时的感觉:
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. 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;
    }
}

消费者

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("退出消费者线程!");
        }
    }


}

测试类

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();
    }
}
并发社区文章

并发章节

多线程顺序打印总结

多线程顺序打印题


新的疑惑

  • 制造死锁,观察死锁导致的CPU飙高
  • 制造优先级
  • 各种方法的操作主体对象是谁。究竟是谁调用谁就让出?
    还是无论是谁主动调这个方法,被作用的对象总是当前在跑的那个线程??
  • yield和notify打配合?
  • yield会让出锁?但sleep不会,所以用sleep记得设置超时时间
  • jvm对线程的调度是无序的!这一点超级重要,开发者是无法百分百控制优先级的。
    因为会出现这样的情况,尽管某线程已经主动将资源让出,但是由于jvm对线程的调度是无序的,
    那么就会出现第二次jvm还是选中该线程继续执行的情况!!
  • 如果要加锁,那么锁的粒度越小越好,不要影响并发!!
  • 怎么往线程里传入参数?(构造方法,set方法,回调函数)回调方法体现的是解耦思想
  • 回调函数分为:同步回调,异步回调
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值