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

04、JAVAEE---多线程进阶、文件I/O、网络初识

多线程之八股文🙂


一、锁策略

加黄部分就是 synchronized锁 的性质

1、乐观锁vs悲观锁

  • 悲观锁:先锁后操作,适合高冲突场景

  • 乐观锁:先操作后检查,适合低冲突场景

2、重量级锁vs轻量级锁

重量级锁是“挂起等待”(让出CPU,省资源但慢),轻量级锁是“自旋”(占着CPU死等,费资源但快)

重量级锁 = 挂起等待,轻量级锁 = 自旋
两组概念讲的是同一件事,只是角度不同:一组强调开销,一组强调行为。


3、挂起等待锁vs自旋锁

挂起等待是“睡觉等”,不占CPU但响应慢;自旋是“站着等”,占CPU但响应快

4、公平锁 vs非公平锁

公平锁 = 排队追女神,先来后到
非公平锁 = 半路截胡,谁抢到是谁的

公平锁严格按等待顺序获取锁(先来后到)

非公平锁允许插队(后请求的线程可能先获得锁)


5、可重入锁vs 不可重入锁

可重入锁同一线程能反复拿锁,不可重入锁会自己锁自己

6、普通互斥锁vs 读写锁

互斥锁读写都互斥,读写锁读不互斥、写才互斥


二、JVM 的优化机制

这三个概念是 JVM 对synchronized优化机制,目的是在不影响正确性的前提下,减少锁带来的性能开销

锁升级

锁只能升级不能降级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

  1. 锁升级:竞争越激烈,锁越重

  2. 锁消除:单线程用的锁,直接去掉

  3. 锁粗化:反复开关太费事,合并成一次

三、CAS操作

CAS 是 CPU 级别的原子操作,比较并交换,成功就改,失败就重试

优点:

不挂起线程(指让一个正在运行的线程主动或被动地放弃 CPU 使用权,进入“等待”状态)、

缺点:

ABA 问题、自旋耗 CPU

一个值从 A 变成 B 又变回 A,CAS 会认为没变过,解决方法是加版本号


四、Runnable 与 Callable

“Runnable 和 Callable 都是任务接口,主要区别有两点:

  1. 返回值:Callable 有泛型返回值,Runnable 是 void;

  2. 异常:Callable 可以抛出受检异常,Runnable 只能在内部处理。

FutureTask是两者的桥梁,因为它既实现了 Runnable(可被 Thread 或线程池执行),又实现了 Future(可保存结果)

使用时,把 Callable 包装进 FutureTask,交给线程执行,最后调用futureTask.get()获取结果

五、synchronized 与 ReentrantLock

  • synchronized是 JVM 层面的关键字,用法简单,自动加锁解锁,但只能非公平、死等

  • ReentrantLock是 Java 类库提供的,需要手动lock/unlock,优点是支持tryLock(可以超时或立即返回),支持公平锁,还能用Condition实现多个等待队列(比如"生产者-消费者场景用两个 Condition 分别控制满和空")

  • ReentrantLock 不会自动释放锁,必须手动调用unlock(),而finally能保证无论是否发生异常,锁都能被释放

总结:两者都是可重入锁

简单场景用synchronized,需要公平锁或超时等待时用ReentrantLock


六、Semaphore

Semaphore 是信号量,用于控制同时访问资源的线程数。它的acquire()在拿不到许可时会让线程阻塞(挂起),release()用于归还许可并唤醒等待线程。两者与ReentrantLocklock()/unlock()类似,都是基于 AQS 实现的。

和 synchronized 不同的是,Semaphore 没有 JVM 层面的偏向锁、自旋锁优化,它是直接通过 AQS 进行阻塞唤醒。

不过在实际开发中,大部分简单同步场景用 synchronized 就够了,代码简洁且 JVM 会自动优化。除非需要限流(比如数据库连接池控制并发数),才会用 Semaphore

七、CountDownLatch

"CountDownLatch 是 JUC 包下的同步工具,让一个或多个线程等待,直到一组操作完成。

核心方法是await()countDown()

  • await():线程在这里挂起(阻塞),直到计数器归零

  • countDown():计数器减 1,归零时唤醒所有等待的线程

常见用法有两个:

  1. 主线程等待子任务:主线程await(),每个子线程做完调用countDown()

  2. 多个线程同时启动:所有线程先await(),由另一个线程一次性countDown()触发

join()的区别join()只能等一个线程死掉,CountDownLatch可以等一组操作(不一定是线程结束,只要调了countDown()就行)

八、Hashtable 和 ConcurrentHashMap

Hashtable(铺垫痛点):

Hashtable 是一把大锁锁整个 Map,用synchronized修饰方法,同一时间只有一个线程操作,性能很差

ConcurrentHashMap 的并发优化:

  1. 给每个链表都安排一把锁—— 不同链表的操作互不影响,可以并发执行。JDK 1.7 是分段锁(16个 Segment,相当于 16 把锁),JDK 1.8 之后进一步细化为锁单个桶(链表头或红黑树根),并发度更高。

  2. size 使用 CAS 进行更新—— 不需要加锁就能原子更新计数器,减少锁竞争。

  3. 扩容化整为零—— 不是一次性迁移所有数据,而是把整个扩容任务拆分成多个小任务,每次迁移一部分,允许其他线程边读边写边帮忙扩容,避免卡顿。


九、文件三面试题

1、遍历目录(基础版)

package javaee; import java.io.File; //基础版遍历目录 public class Demo23 { public static void main(String[] args) { //绝对路径 File dir = new File("E:/my-first-web"); //先判断是不是目录 if(!dir.isDirectory()){ System.out.println("目录不存在或不是文件夹:" + dir.getAbsolutePath()); return; } //1、当前目录下所有文件和目录的名字 String[] names = dir.list(); System.out.println("=====所有文件和目录名字====="); for(String name : names){ System.out.println(name); } //2、当前目录下所有文件和目录的 File 对象 File[] files = dir.listFiles(); System.out.println("=====所有对象====="); for (File file : files){ if(file.isFile()){ System.out.println("[文件]" + file.getName()); } if (file.isDirectory()){ System.out.println("[目录]" + file.getName()); } } } }

这是一个基础版本的文件遍历,若想更装杯,可以使用递归来写😎


2、复制文件

package javaee; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Scanner; public class Demo24 { public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); System.out.print("请输入要复制的文件(绝对路径 OR 相对路径):"); String sourcePath = scanner.next(); //创建原文件对象 File sourceFile = new File(sourcePath); if (!sourceFile.exists()) { System.out.println("文件不存在,请确认路径是否正确"); return; } if (!sourceFile.isFile()) { System.out.println("文件不是普通文件,请确认路径是否正确"); return; } System.out.print("请输入要复制的目标路径(绝对路径 OR 相对路径):"); String destPath = scanner.next(); //创建复制完成的文件对象 File destFile = new File(destPath); if (destFile.exists()) { if (destFile.isDirectory()) { System.out.println("目标路径已经存在,并且是一个目录,请确认路径是否正确"); return; } if (destFile.isFile()) { System.out.print("目标文件已经存在,是否要进行覆盖?(y/n):"); String ans = scanner.next(); if (ans.equals("y")) { System.out.println("开始覆盖复制..."); } else { System.out.println("停止复制"); return; } } } // ========== 真正的复制代码 ========== try (FileInputStream fis = new FileInputStream(sourceFile); FileOutputStream fos = new FileOutputStream(destFile)) { byte[] buffer = new byte[8192]; int len; while ((len = fis.read(buffer)) != -1) { fos.write(buffer, 0, len); } } System.out.println("复制完成!目标文件位置:" + destFile.getAbsolutePath()); } }

3、复制整个目录

package javaee; import java.io.*; import java.util.*; public class Demo25 { public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); System.out.print("请输入要复制的源目录:"); String srcPath = scanner.next(); File srcDir = new File(srcPath); if (!srcDir.isDirectory()) { System.out.println("源目录不存在或不是文件夹"); return; } System.out.print("请输入目标目录:"); String destPath = scanner.next(); File destDir = new File(destPath); // 调用复制目录的方法(遍历 + 复制) copyDirectory(srcDir, destDir); System.out.println("目录复制完成!"); } // 复制目录(结合了遍历 + 复制) public static void copyDirectory(File srcDir, File destDir) throws IOException { // 1. 创建目标目录(遍历的第一步) if (!destDir.exists()) { destDir.mkdirs(); } // 2. 遍历源目录(来自 Demo23 的思想) File[] children = srcDir.listFiles(); if (children != null) { for (File child : children) { File destChild = new File(destDir, child.getName()); if (child.isFile()) { // 3. 复制文件(来自 Demo24 的核心代码) copyFile(child, destChild); } else if (child.isDirectory()) { // 4. 遇到子目录,递归调用自己(继续遍历 + 复制) copyDirectory(child, destChild); } } } } // 复制单个文件(来自 Demo24 的核心代码) private static void copyFile(File src, File dest) throws IOException { try (FileInputStream fis = new FileInputStream(src); FileOutputStream fos = new FileOutputStream(dest)) { byte[] buffer = new byte[8192]; int len; while ((len = fis.read(buffer)) != -1) { fos.write(buffer, 0, len); } } System.out.println("复制文件:" + src.getName()); } }

相当于前两者的结合


十、网络基本盘

1、OSI 七层 / TCP/IP 四层

网络一般看TCP/IP 四层模型应用层、传输层、网络层、网络接口层。数据发送时从上往下封装(每层加报头),接收时从下往上分用(每层拆报头)

2、Socket编程

Socket 是应用层与传输层之间的"桥梁",让你不用关心下面两层(网络层、网络接口层)的细节

TCP/IP 四层

负责什么

Socket 的角色

应用层

你的程序(浏览器、微信等)

你写代码的地方

↑↑↑↑↑↑↑↑↑↑↑↑↑

Socket 接口在这里

↑↑↑↑↑↑↑↑↑↑↑↑↑

传输层

TCP/UDP(端口)

Socket 帮你调用

网络层

IP(路由)

Socket 帮你调用

网络接口层

MAC、网卡(物理传输)

Socket 帮你调用


3、TCP 和 UDP 有什么区别?

主要有三点区别:

  1. 连接:TCP 有连接,通信前要先保存对方的 IP 和端口(三次握手);UDP 无连接,不保存对方信息,直接发

  2. 可靠性:TCP 可靠传输(牺牲效率),有确认和重传;UDP 不可靠,丢了不管

  3. 数据方式:TCP 面向字节流,没有边界;UDP 面向数据报,有边界

另外两者都是全双工(可以同时收发,即同一时刻双向通信)


4、TCP 和 UDP 在“数据传输方式”上的解释

TCP(面向字节流)

想发送 100 个字节,可以 1 次全发,也可以分 10 次每次发 10 字节,还可以分 100 次每次发 1 字节

本质:TCP 不关心你分几次发,只关心你发了多少字节。接收方也按字节流读,不知道你原来分了几次

类比:像水管里的水,你倒一桶水进去,出来也是一桶水,但中间是不分“块”的

UDP(面向数据报)

一次必须是发送/接收一个完整的 UDP 数据报,不能是半个

本质:UDP 有边界,发的时候是一个完整的包,收的时候也必须一个完整的包收

类比:像寄快递,你发一个包裹,对方收到一个包裹,不会收到半个


5、TCP和UDP的Socket实现区别

代码实现对比

维度

TCP Socket

UDP Socket

核心类

ServerSocket/Socket

DatagramSocket

数据传输

基于(Stream),数据无边界

基于数据报(Datagram),每个包独立

连接方式

需要accept()建立连接(阻塞)

无需连接,直接发

收发方法

getInputStream()/getOutputStream()

send(DatagramPacket)/receive(DatagramPacket)

丢包处理

底层自动重传,保证到达

不保证,可能丢包/乱序

服务端标识

每个客户端有独立Socket

只有一个DatagramSocket,靠包内地址区分


追问1:那TCP服务端怎么同时处理多个客户端?

①多线程

优点:简单直观,每个客户端逻辑独立

缺点

  • 1个客户端 = 1个线程,1000个客户端 = 1000个线程

  • 线程切换开销大,内存占用高(每个线程约1MB栈空间)

  • C10K问题(1万并发就扛不住了)

再问:线程池能解决吗?

能缓解,但不能根治。线程池限制了最大线程数,超出就得排队或拒绝,本质还是"一个连接一个线程"的模式

②IO多路复用

优点

  • 一个线程能管理成千上万个连接

  • 没有线程切换开销

  • 内存占用低

一个线程同时监控多个socket,哪个有数据就读哪个


追问2:epoll的ET vs LT?(边缘触发 vs 水平触发)

模式

一句话解释

行为

LT(水平触发)

有数据就一直通知你

缓冲区有数据,每次调用epoll_wait都会通知

ET(边缘触发)

新数据来了只通知一次

只在状态从"无数据→有数据"时通知一次

回答

LT是默认模式,更安全,但可能重复通知;ET性能更高,但要求用户一次性把数据读完,否则数据会丢。Redis、Nginx用ET追求极致性能


本章完

http://www.cnnetsun.cn/news/2875799.html

相关文章:

  • OSPF综合实验(nat,汇总,特殊区域,加快收敛,安全认证)
  • 2026年AI人才市场火爆!这3个高薪岗位普通人也能入场?速收藏!
  • 哈希表冲突处理:开放寻址与拉链法的底层实现与工程选型
  • 深度解析AKShare Pro数据接口:从基础使用到高级配置
  • 企业微信自动化中验证环节的处理策略
  • 终极Project Sekai表情包制作指南:3分钟创建个性化Discord贴纸
  • pyarrow,一个列式数据处理的 Python 库!
  • Pentaho Data Integration 11.x架构演进与关键技术实现深度解析
  • 计算机毕设实战-基于 Java 的智能土地档案综合管理系统 土地信息与档案管控平台基于SpringBoot的油田土地档案管理系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 深入解析汽车级LCD段码驱动芯片PCA8576D:从原理到实战应用
  • 企业知识产权管理痛点与解决方案系列解说十
  • Python通达信数据接口:三步掌握A股行情分析的免费神器
  • MPV懒人包终极指南:5分钟让Windows用户享受专业影院级播放体验
  • 3步释放华硕笔记本潜能:G-Helper轻量控制中心完全指南
  • 3分钟掌握:如何在Kodi中无缝播放115网盘视频
  • 【RT-DETR实战】RT-DETR实战手记(200):端侧实时目标检测,下一步往哪儿走?
  • 手把手教你用C#和BouncyCastle实现IC卡SM4国密算法(含密钥分散与MAC计算)
  • 贵港车棚供应商是什么?主要有哪几种类型?
  • 终极指南:如何高效使用PKSM进行跨世代宝可梦存档管理
  • Nintendo Switch游戏文件管理终极指南:NSC_BUILDER完全使用教程
  • 别再傻傻遍历二维数组了!用C语言三元组高效搞定稀疏矩阵加法(附PTA真题避坑指南)
  • Windows 11终极优化指南:Win11Debloat一键清理系统冗余与隐私保护
  • 华为MetaERP Oracle EBS(R12)用间接法编制现金流量表,从原理→前提→配置→FSG 搭建→公式设计→测试→月结操作→常见坑完整、一步一步讲清楚,你可以直接照着做实施。
  • 如何在老旧Mac上安装最新macOS:OpenCore Legacy Patcher完整4步指南
  • P87LPC778中断与I/O配置实战:从寄存器详解到避坑指南
  • Java毕业设计-基于jspm自行车个性化改装推荐系统基于springboot框架的自行车个性化改装推荐系统(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • 从方格游戏到动态规划:用Python手把手解‘踩方格’问题(附两种递推思路对比)
  • Windows 11优化指南:用Win11Debloat一键清理系统垃圾,提升电脑性能
  • 终极指南:Windows 11 LTSC系统完美添加微软商店完整方案
  • 模糊控制:从洗衣到工业,如何让机器像人一样“思考”