当前位置: 首页 > news >正文

JAVA后端面试笔记(二)

《关于并发编程的面试笔记》

1.Synchronized和Volatile关键词

1.1 Synchronized和Volatile的异同

同:都是Java中的关键词,都可实现线程同步

异:

volatile 关键字只能用于单个变量,而synchronized适用于方法及代码块

②volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。

volatile 关键字主要用于解决单个变量在多个线程之间的可见性,而 synchronized 关键字解决的是多线程之间的资源同步

1.2 volatile的使用原理

volatile 可以确保对某个变量的更新对其他线程马上可见 ,一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存,当其他线程读取该共享变量,会从主内存获取最新值,而不是使用当前线程的本地内存值。

1.3 用途区别

synchronized用于控制代码块/方法的线程安全访问,使用于资源共享

volatile用于状态控制(如线程停止标志)

2.多线程实现的四种方式?

根据是否可获取返回值分为两种!

不可获取返回值:继承Thread类,实现Runnable接口(本质都是重写run()方法

可获取返回值:实现Callable接口,线程池

2.1 继承Thread类

public class MyThread extends Thread {public void run() {while (true) {System.out.println(this.getName() + " is running");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}}

2.2 实现Runnable接口

实现Runnable接口的实现类的实例对象作为Thread构造函数的target

public class TicketSailTask implements Runnable {public static int number = 100;//总共100张票public Object lock = new Object();//创建锁@Overridepublic void run() {while (true) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized(lock) {if (number > 0) {System.out.println(Thread.currentThread().getName() + "正在卖第" + number + "张票");number--;} else {break;}}}}public static void main(String[] args) {TicketSailTask ticketSailTask = new TicketSailTask();// 三个线程ticketSailTask,共用一个锁对象new Thread(ticketSailTask,"小志").start();// 使用锁对象:ticketSailTask.classnew Thread(ticketSailTask,"小庄").start();// 使用锁对象:ticketSailTask.classnew Thread(ticketSailTask,"小王").start();// 使用锁对象:ticketSailTask.class}}

2.3 Callable和Future Task

特点:可获取到返回值!FutureTask通常只能被执行一次

FutureTask的get()用于阻塞主线程直至获取到执行结果

public class CallableImpl implements Callable<String> {String acceptStr;public CallableImpl(String acceptStr) {this.acceptStr = acceptStr;}@Overridepublic String call() throws Exception {// 任务阻塞一秒Thread.sleep(1000);return this.acceptStr+"添加字段并返回";}public static void main(String[] args) throws ExecutionException, InterruptedException {CallableImpl myTest1 = new CallableImpl("线程1");CallableImpl myTest2 = new CallableImpl("线程2");CallableImpl myTest3 = new CallableImpl("线程3");FutureTask<String> task1 = new FutureTask<>(myTest1);FutureTask<String> task2 = new FutureTask<>(myTest2);FutureTask<String> task3 = new FutureTask<>(myTest3);long beginTime = System.currentTimeMillis();// 创建线程new Thread(task1,"线程1").start();new Thread(task2,"线程2").start();new Thread(task3,"线程3").start();List resultList = new ArrayList<>();// 调用get()阻塞主线程,反之,线程不会阻塞String result1 = task1.get();String result2 = task2.get();String result3 = task3.get();resultList.add(result1);resultList.add(result2);resultList.add(result3);long endTime = System.currentTimeMillis();System.out.println("开始: " + resultList);System.out.println("用时 : " + (endTime - beginTime)/1000 + "s");}
}
开始: [线程1添加字段并返回, 线程2添加字段并返回, 线程3添加字段并返回]
用时 : 1s

2.4 线程池创建线程

注意:线程池记得使用后关闭,避免造成资源泄漏

public class ThreadPool {// 定义要创建的线程数private static int THREAD_NUM=10;public static void main(String[] args) throws InterruptedException {// 线程池有5个固定线程ExecutorService executorService = Executors.newFixedThreadPool(5);for (int i = 0; i < THREAD_NUM; i++) {RunnableThread thread = new RunnableThread();Thread.sleep(1000);executorService.execute(thread);}// todo 关闭线程池executorService.shutdown();}static class RunnableThread implements Runnable{@Overridepublic void run() {System.out.println("通过线程池方式创建的线程:" + Thread.currentThread().getName() + " ");}}
}

3. 项目中哪些地方用到了多线程?

略......

3.1 CountDownLatch的用法

原理:多线程进行递减计数,等到计数到0的时候等待即不再阻塞,从而向下执行。

await()当前线程阻塞,直至计数器到0主线程才可以启动。

/*** @author zzp* @date 2025/9/15/周一 10:42* @description: 主线程主动阻塞等待子线程执行结束再继续执行的案例 -- 使用CountDownLatch()*/public class CountDownLatchDemo {// 创建一个CountDownLatch实例private static volatile CountDownLatch countDownLatch = new CountDownLatch(2);public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(new Runnable() {public void run() {try{Thread.sleep(1000);System.out.println("线程1结束");}catch (Exception e){e.printStackTrace();}finally {countDownLatch.countDown();}}});Thread thread2 = new Thread(new Runnable() {public void run() {try {Thread.sleep(1000);System.out.println("线程2结束");}catch (Exception e){e.printStackTrace();}finally {countDownLatch.countDown();}}});// 开启两个子线程thread1.start();thread2.start();System.out.println("主线程等待子线程结束...");countDownLatch.await();System.out.println("所有子线程结束......");}}
主线程等待子线程结束...
线程2结束
线程1结束
所有子线程结束......

CountdownLatch实现多线程并可获取到返回值

异步多线程实现导出写表操作,使用CountDownLatch.await( )实现子线程执行完毕,主线程结束阻塞操作......

    @Overridepublic void exportToExcel(HttpServletResponse response, HbCusReportDto hrd, Map<String, Object> headMap, String fileNamePrefix, String exportFormat) {// TODO 获取数据数量 totalDataSize// TODO 计算需要多少批次 numPages // TODO 获取表头// TODO 设置导出文件名// TODO 根据导出的文件格式-设置响应头信息// 创建线程池,最大线程数为 numPages,假设每个分页查询由一个线程处理// 用于存放查询结果的列表,使用一个 Map 保证顺序 -- allDataListMapMap<Integer, List<List<Object>>> allDataListMap = new TreeMap<>();try (ExcelWriter writer = EasyExcel.write(response.getOutputStream()).excelType(ExcelTypeEnum.XLSX).build()) {// 使用 CountDownLatch 等待所有线程执行完成CountDownLatch latch = new CountDownLatch((int) numPages);// 使用线程提交分页查询任务for (int pageIndex = 0; pageIndex < numPages; pageIndex++) {final int finalPageIndex = pageIndex + 1;long finalPageSize = pageSize;myThreadPool.submit(() -> {try {Map<String, Object> map = selectPageByCondition(hrd.getId(), hrd.getSqlStr(), hrd.getFieldList(), hrd.getParamsList(), (int) finalPageIndex, finalPageSize, hrd.getUserId(), hrd.getWarehouse());IPage<Map<String, String>> iPage = (IPage<Map<String, String>>) map.get("data");List<List<Object>> dataList = ExcelUnits.convertToDataList(iPage.getRecords(), headMap);// 保证按页号顺序存储allDataListMap.put(finalPageIndex, dataList);} catch (Exception e) {log.error("Error during page query for page {}", finalPageIndex, e);} finally {latch.countDown();}});}latch.await();  // ***等待所有分页任务完成***// 按照页号顺序处理查询结果并写入 Excelint currentSheetIndex = 0;int currentSheetDataCount = 0;for (List<List<Object>> dataList : allDataListMap.values()) {if (currentSheetDataCount + dataList.size() > maxRowsPerSheet) {currentSheetIndex++;currentSheetDataCount = 0;}String sheetName = "Sheet" + (currentSheetIndex + 1);WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).head(headList).registerWriteHandler(new CustomWidthStyleStrategy(1)).build();writer.write(dataList, writeSheet);currentSheetDataCount += dataList.size();}} catch (IOException | InterruptedException e) {log.error("Error during export process.", e);Thread.currentThread().interrupt(); // 恢复线程中断状态}}

3.2 使用阻塞队列实现顺序消费

案例:(手机)生产-打包-发运-消费 的顺序执行

--------------------------------------------------Phone手机实体类--------------------------------------------------

public class Phone {/*** 手机的状态* PRODUCED:已生产* PACKED:已打包* DELIVERED:已发货*/public enum Status{PRODUCED,PACKED,DELIVERED}// 默认状态--已生产private Status status = Status.PRODUCED;private final int id;public Phone(int id) {this.id = id;}public void pack(){status = Status.PACKED;}public void deliver(){status = Status.DELIVERED;}public int getId(){return id;}public Status getStatus(){return status;}@Overridepublic String toString() {return "Phone id: " + id + ", status: " + status;    }
}

--------------------------------------------------阻塞队列Queue--------------------------------------------------

public class PhoneQueue extends LinkedBlockingQueue<Phone> {
}

--------------------------------------------------生产手机的任务线程--------------------------------------------------

public class Producer implements Runnable {private PhoneQueue phoneQueue;private int count = 0;private Random random = new Random(47);public Producer(PhoneQueue phoneQueue) {this.phoneQueue = phoneQueue;}@Overridepublic void run() {try {while (!Thread.interrupted()) {TimeUnit.MICROSECONDS.sleep(300 + random.nextInt(500));// 生产有序的手机Phone phone = new Phone(count++);System.out.println(phone);phoneQueue.add(phone);}}catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("Producer is off");}}

--------------------------------------------------打包手机的任务线程--------------------------------------------------

take(): 获取并移除此队列的头部,在元素变得可用之前一直等待 。queue的长度 == 0 的时候,一直阻塞,直到获取到元素值,程序继续......

public class Packer implements Runnable{private PhoneQueue producedQueue;private PhoneQueue packedQueue;public Packer(PhoneQueue producedQueue, PhoneQueue packedQueue) {this.producedQueue = producedQueue;this.packedQueue = packedQueue;}@Overridepublic void run() {try{while(!Thread.interrupted()){// 在取得下一个手机之前会一直阻塞Phone phone = producedQueue.take();phone.pack();System.out.println(phone);packedQueue.put(phone);}}catch(InterruptedException e){System.out.println("Packer thread interrupted");}System.out.println("Packer done");}
}

--------------------------------------------------发运手机的任务线程--------------------------------------------------

public class Delivery implements Runnable {private PhoneQueue packedQueue;private PhoneQueue finishQueue;public Delivery(PhoneQueue packedQueue, PhoneQueue finishQueue) {this.packedQueue = packedQueue;this.finishQueue = finishQueue;}@Overridepublic void run() {try{while (!Thread.interrupted()) {Phone phone = packedQueue.take();phone.deliver();System.out.println(phone);finishQueue.put(phone);}}catch (InterruptedException e){System.out.println("Delivery interrupted");}System.out.println("Delivery off");}
}

--------------------------------------------------消费手机的任务线程--------------------------------------------------

public class Consumer implements Runnable {private PhoneQueue finishedQueue;private int count = 0;public Consumer(PhoneQueue finishedQueue) {this.finishedQueue = finishedQueue;}@Overridepublic void run() {try{while (!Thread.interrupted()) {Phone phone = finishedQueue.take();if (phone.getId() != count++|| phone.getStatus() != Phone.Status.DELIVERED){System.out.println("Error -> " + phone);System.exit(-1);}else {System.out.println(phone + " -> User");}}}catch (InterruptedException e){System.out.println("Consumer interrupted");}System.out.println("Consumer off");}
}

-----------------------------------------------------------主线程-------------------------------------------------------------

public class ConsumerMain {public static void main(String[] args) {PhoneQueue producedQueue = new PhoneQueue();PhoneQueue packedQueue = new PhoneQueue();PhoneQueue deliveredQueue = new PhoneQueue();ExecutorService executor = Executors.newCachedThreadPool();executor.execute(new Producer(producedQueue));executor.execute(new Packer(producedQueue,packedQueue));executor.execute(new Delivery(packedQueue,deliveredQueue));executor.execute(new Consumer(deliveredQueue));try{TimeUnit.SECONDS.sleep(5);}catch (InterruptedException e){e.printStackTrace();}executor.shutdown();}
}

================================结果====================================

Phone id: 0, status: PRODUCED
Phone id: 0, status: PACKED
Phone id: 0, status: DELIVERED
Phone id: 0, status: DELIVERED -> User
Phone id: 1, status: PRODUCED
Phone id: 1, status: PACKED
Phone id: 1, status: DELIVERED
Phone id: 1, status: DELIVERED -> User
......

4. 多线程同步的方案

synchronized关键词:

5. 为什么使用线程池?线程池的优缺点?

-- 避免频繁创建线程和销毁线程(使线程复用)

线程池的优点:

1.降低资源消耗-通过重复利用已创建的线程降低线程创建和销毁造成的资源消耗

2.提高响应速度-任务可以不需要等待线程创建就能立刻执行

3.提高线程的可管理性-线程池可以进行统一的分配,调优和监控

缺点:多线程会占CPU,使用多线程的地方并发量比较高时会导致其他功能响应很慢。  


文章转载自:

http://WVJy9ynj.dxqwm.cn
http://cOV9noxY.dxqwm.cn
http://LFJZbuj3.dxqwm.cn
http://IibDsprs.dxqwm.cn
http://F6XhmtLs.dxqwm.cn
http://LORzngWg.dxqwm.cn
http://4xmHbNRY.dxqwm.cn
http://aG1c5Wpj.dxqwm.cn
http://OSmueMf7.dxqwm.cn
http://QZQkpuUh.dxqwm.cn
http://rQCyha7N.dxqwm.cn
http://c65cOadd.dxqwm.cn
http://XlM0Djpz.dxqwm.cn
http://kCHHLDmR.dxqwm.cn
http://9qoSilb2.dxqwm.cn
http://cO0mywoT.dxqwm.cn
http://fVvhoUz8.dxqwm.cn
http://TJAjp40N.dxqwm.cn
http://DFqF6rBH.dxqwm.cn
http://E7LkLdPk.dxqwm.cn
http://FvC7tvJl.dxqwm.cn
http://Cuilui3k.dxqwm.cn
http://cWdKgUzT.dxqwm.cn
http://PBRRox5h.dxqwm.cn
http://x6VhwH6s.dxqwm.cn
http://XXJKazio.dxqwm.cn
http://KCfWHKaI.dxqwm.cn
http://KtQwkP97.dxqwm.cn
http://Gu9CE4QN.dxqwm.cn
http://dA3c4Wc5.dxqwm.cn
http://www.dtcms.com/a/386217.html

相关文章:

  • 每日前端宝藏库 | fullPage.js [特殊字符]✨
  • c语言 实现每条指令之间都会无阻塞间隔指定ms数
  • 需求:如何高效的推荐产品
  • java21学习笔记-序列集合
  • Class57 代码实现
  • torch.gather
  • 自学嵌入式第四十二天:单片机-定时器和UART串口
  • 大数据毕业设计选题推荐-基于大数据的旅游网站用户行为数据分析系统-Hadoop-Spark-数据可视化-BigData
  • 深入浅出数据结构:队列(Queue)—— 生活中的排队艺术
  • spring通过Spring Integration实现udp通信
  • Linux内存管理章节十八:内核开发者的武器库:内存分配API实战指南
  • CAD如何输出PDF多页文件
  • 我对 WPF 动摇时的选择:.NET Framework 4.6.2+WPF+Islands+UWP+CompostionApi
  • 1.整流-滤波电路的缺点和PFC的引入
  • QT 项目 线程信号切换 举例
  • 构网型5MW中压储能变流升压一体机技术方案
  • 【数据工程】8. SQL 入门教程
  • C++---前向声明
  • 在Qt项目中使用QtConcurrent::run,实现异步等待和同步调用
  • 经验分享只靠口头传递会带来哪些问题
  • Linux底层-内核数据接口:/proc
  • PEFT+DeepSpeed 1 (微调 分布式 显存优化)
  • Spring Boot 下 Druid 连接池:多维度优化打造卓越性能
  • 提升学术研究能力:从开题构思难题到AI辅助提纲生成
  • spring-kafka的消息拦截器RecordInterceptor
  • VSCode + Python 开发踩坑:虚拟环境不在项目根目录导致包无法识别该怎么办
  • 【MCP】【FastMCP】[特殊字符] 使用 UV 创建 FastMCP 服务完整示例
  • 蓝绿部署(Blue-Green Deployment)介绍(一种用于降低软件发布风险的部署策略)流量切换(金丝雀发布)
  • 羽毛球地板:从专业运动场景到全民健身市场的技术跃迁与产业重构
  • 【实战】预警算法--噪声添加机制