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

JavaEE必会面试题,从线程讲到线程安全,一文带你通过多线程面试

目录

一、讲讲线程与进程的联系

二、线程创建的五种写法

三、产生线程安全问题的五大原因

四、线程的状态有哪几种?

五、死锁产生的四个必要条件[🌟背下来]

六、如何避免死锁?

七、请你写一个懒汉模式线程安全的代码并解释你这样写的原因

🌟你的回答:


🌟从这个专题开始,我将开始讲解一些Java后端面试准备过程中必须具备的素养与能力,废话不多说,今天主要学习的是Java多线程编程中最最重要的基础面试题,也是工作中最可能用到的内容。

一、讲讲线程与进程的联系

首先解释定义,即进程是计算机运行起来的应用程序,如下图:

线程是轻量级进程,和进程的关系是:一个进程包含多个线程。

他们之间的区别重点记忆:

1. 每个进程有自己独立的资源,进程与进程间不能共享;而同一个进程的线程之间共享相同的资源。

2. 进程之间不会相互影响,进程挂了不会影响其他进程,而同一个进程的某个线程挂了有可能把其他线程带走。

3. 进程之间通常不会有“资源冲突”的情况,而同一个进程的线程之间特别容易产生资源访问冲突。

4. 进程是操作系统资源分配的基本单位,线程是操作系统调度执行的基本单位


二、线程创建的五种写法

1. 继承Thread,重写run方法(run方法是多线程代码执行的入口,由start调用)

package thread; // 创建一个新的类, 让这个类继承标准库的 Thread 类 class MyThread extends Thread { // 重写父类的 run 方法. @Override public void run() { while (true) { System.out.println("hello thread"); // 休息 1000ms => 1s try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } // Demo => 例子~~ Example Sample Tutor 入门的例子 public class Demo1 { public static void main(String[] args) throws InterruptedException { // 1. 创建 Thread 的实例 // MyThread t = new MyThread(); Thread t = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); // 2. 启动线程 t.start(); t2.start(); t3.start(); // t.run(); while (true) { System.out.println("hello main"); Thread.sleep(1000); } } }

2. 实现Runnable接口,搭配Thread

package thread; import java.util.Scanner; class MyRunnable implements Runnable { @Override public void run() { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo2 { public static void main(String[] args) throws InterruptedException { MyRunnable r = new MyRunnable(); Thread t = new Thread(r); t.start(); while (true) { System.out.println("hello main"); Thread.sleep(1000); } } }

3. 继承Thread,使用匿名内部类

package thread; public class Demo3 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { @Override public void run() { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; // 记住, 调用 start 才是真正创建线程. t.start(); while (true) { System.out.println("hello main"); Thread.sleep(1000); } } }

4. 继承Runnable,使用匿名内部类

package thread; public class Demo4 { public static void main(String[] args) throws InterruptedException { // Runnable runnable = new Runnable() { // @Override // public void run() { // // } // }; // Thread t = new Thread(runnable); Thread t = new Thread(new Runnable() { @Override public void run() { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }); t.start(); while (true) { System.out.println("hello main"); Thread.sleep(1000); } } }

5. Lambda表达式(最常用!!!)

package thread; public class Demo5 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); while (true) { System.out.println("hello main"); Thread.sleep(1000); } } }

三、产生线程安全问题的五大原因

1.根本原因:操作系统对于线程的调度是随机的(这个我们改不了)

2.两个线程对同一个变量进行修改操作(不是常见的Java应对方式)

2.1 一个线程针对一个变量进行修改 => 没问题

2.2 两个线程针对不同变量进行修改 => 没问题

2.3 两个线程针对同一个变量进行读取 => 没问题

针对2.1->不使用多线程 => 单线程 没法充分利用多核 CPU 资源~

针对2.2->不使用同一个变量, 每个线程搞一个变量 (比较吃的需求和逻辑的)

3.修改操作不是原子的->解决方式,使用锁比如synchronized

4.内存可见性问题:由于编译器优化产生的线程安全问题,可以从JMM模型(主内存和工作内存)或者硬件的角度去给面试官解释(一句话:“从寄存器或者缓存中读数据而不是从内存中读数据”)--解决:使用volatile

5.指令重排序问题:只有单例模式一个孤证,后面会讲有关懒汉模式的线程安全的代码


四、线程的状态有哪几种?

(1)宏观上理解分为阻塞态和就绪态

(2)微观上理解分为

NEW:创建了线程但是还没有start()

TERMINATED:线程结束了但是Thread对象还没有销毁

RUNNABLE:线程正在CPU上运行,或者正在就绪队列中即将被操作系统调度到CPU上执行

WAITING:死等-->join()

TIMED_WATING:带有超时时间的等待-->join(填写毫秒)

BLOCKED:与锁有关的阻塞


五、死锁产生的四个必要条件[🌟背下来]

1. 锁是互斥的--同一时刻只能有一个线程拿到锁

2. 锁是不可被抢占的

1、2对于synchronized是改不了的

3. 请求和保持:在已经持有一把锁且还未释放的前提下,又去申请另一把锁

(典型的“吃着碗里的,想着锅里的”)

4. 循环等待 / 环路等待:类比车钥匙锁家里,家钥匙锁车里


六、如何避免死锁?

1. 打破请求和保持:避免“锁的嵌套”

2. 打破循环等待:约定好一个加锁的顺序(这里附上一个典型的示例代码如下):

package thread; //5.多线程3 解决典型死锁--打破循环等待--按照一定的顺序来加锁即可 public class Demo18_2 { private static void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } } public static void main(String[] args) { Object locker1 = new Object(); Object locker2 = new Object(); Thread t1 = new Thread(() -> { synchronized (locker1) { System.out.println("t1 获取到 locker1"); sleep(1000); synchronized (locker2) { System.out.println("t1 获取到 locker2"); } } }); Thread t2 = new Thread(() -> { synchronized (locker1) { System.out.println("t2 获取到 locker1"); sleep(1000); synchronized (locker2) { System.out.println("t2 获取到 locker2"); } } }); t1.start(); t2.start(); } }

七、请你写一个懒汉模式线程安全的代码并解释你这样写的原因

package thread; // 懒汉的单例模式 class SingletonLazy { private volatile static SingletonLazy instance = null; // 作为要加锁的对象. 由于是要在 static 方法中使用锁对象, 对象本身也得是 static 的. private static Object locker = new Object(); // 懒汉模式的关键在于, 把实例的创建时机推迟了, 推迟到第一次使用的时候, 创建. public static SingletonLazy getInstance() { // 这个条件判定是否需要加锁 if (instance == null) { synchronized (locker) { // 判定是否需要创建实例 if (instance == null) { instance = new SingletonLazy(); } } } return instance; } private SingletonLazy() { } } public class Demo25 { public static void main(String[] args) { SingletonLazy s1 = SingletonLazy.getInstance(); SingletonLazy s2 = SingletonLazy.getInstance(); System.out.println(s1 == s2); } }

🌟你的回答:

“面试官你好,这个懒汉模式的线程安全代码的要点有这么三点,第一使用synchronized保证判断和赋值操作在一起是原子的,保证线程安全;第二,使用双重if,内层if用来判断是否需要创建实例,而外层if判断用来判断是否需要加锁,因为加锁涉及到开销和效率问题;第三,为instance加上volatile关键字来防止因为指令重排序导致的线程安全问题”

特别注意:

懒汉模式只有在第一次创建实例之前会产生线程安全问题,一旦创建实例成功了线程安全问题就消失了,所以为了避免反复加锁带来的开销,需要在synchronized的外层也加上一个if判断语句

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

相关文章:

  • 别被低价带偏,真正该看的是小游戏开发的服务闭环
  • 画镜网络:大型爬虫架构设计思路
  • 传统企业的数据孤岛是如何形成的?
  • 零基础通学全球芯片体系:从沙子到光刻机,CPU/GPU/国产芯片全品类解析
  • Java 集合框架(List, Set, Map)练习题
  • 从数字化到暖心化:“盛情康养”解锁沈阳养老服务新范式
  • Java面试-08-分布式缓存Redis
  • 矢量网络分析仪测量实用指南
  • 轻养新风席卷中秋 鲜品屋2026新品发布会圆满举办
  • 厦门市铧大技术学校打造“AI+”全专业职教新高地
  • 2026年免漆吸塑厂家选择指南:这3点最关键
  • 工装采购如何筛选靠谱厂商
  • 探索光伏储能技术,哪种方案更稳定可靠?
  • CAXA电子图版安装步骤(附安装包)CAXA电子图版2024 超详细下载安装教程
  • 【课程设计/毕业设计】基于 SpringBoot+Vue 的高校师生教学评价服务系统的设计与实现【附源码、数据库、万字文档】
  • HDR图像高斯双边滤波MATLAB实现
  • AI率太高怎么降?10款降AI率软件实测(含免费降ai率工具)真实避坑指南
  • 巯基化海藻酸钠(SH-Alg)水凝胶与琼脂糖(Agarose)凝胶的应用差异
  • RAG:让大模型“开卷考试“的神器,三步搞定知识更新!
  • 什么是 NGINX 日志?
  • XR 无限空间项目案例:从场地规划到现场安装全流程
  • AI 音频平台引入谷歌隐形水印技术,未来几周将覆盖所有音频生成内容
  • 2026零基础语音转文字总结使用场景避坑指南 包教包会可直接上手
  • Seed与Root在密码学中的区别
  • 提高脂质体维生素利用率的关键因素是什么?(科普解析)
  • 户外 4G 报警器方案设计:MP3 录音 + LBS 定位 + 低功耗待机
  • claude目前具备以下技能:
  • 30VOUT,2A,XZ5100,升压LED恒流驱动芯片
  • 互联网医院预约接单微信小程序搭建软件系统开发
  • 工业级AIGC视频重塑汽车广告生态:集之互动以高可控技术实现降本增效