IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 多线程面试八股大总结 -> 正文阅读

[Java知识库]多线程面试八股大总结

目录

一、如何保证线程安全?

二、线程 vs 进程

1、线程的优点

2、进程和线程的区别

三、常见的锁策略

1、乐观锁 vs 悲观锁

2、读写锁

3、重量级锁 vs 轻量级锁

4、自旋锁(Spin Lock)

5、公平锁 vs 非公平锁

6、可重入锁 vs 不可重入锁

面试葵花宝典

1、你是怎么理解乐观锁和悲观锁的,具体怎么实现?

2、介绍下读写锁?

3、什么是自旋锁,为什么要使用自旋锁策略,缺点是什么?

4、synchronized 是可重入锁么?

5、CAS?

1)什么是CAS?

2)CAS 是怎么实现的

3)CAS有哪些应用?

4)CAS 的 ABA 问题

5)高频面试题

6)Synchronized 原理

7) 偏向锁 -->? 轻量级锁 --> 重量级锁

7)锁消除 ?锁粗化?

8)Callable 接口

?9)JUC(java.util.concurrent)的常见类

10)原子类

11)线程池

12)信号量

?13)CountDownLatch

14)相关面试题

?15)线程安全的集合类

16)多线程环境使用 ArrayList?

?17)多线程环境使用队列

18)多线程环境使用哈希表

19)相关面试题

20)死锁?

21)如何避免死锁?

22)其他常见问题


一、如何保证线程安全?

1、使用没有共享资源的模型

2、使用共享资源只读不写的模型

1)不需要写共享资源

2)使用不可变对象

3、直面线程安全(重点)

? ? ? ? 1)保证原子性

? ? ? ? 2)保证顺序

? ? ? ? 3)? ?保证内存可见性

二、线程 vs 进程

1、线程的优点

1)创建一个新的线程的代价要比创建一个新的进程小

2)与进程间切换相比,线程间的切换需要操作系统做的操作小得多

3)线程占用资源比进程少

4)能充分利用多处理器的可并行数量

5)在等待 I/O 操作结束的同时,程序可以执行其他任务

6)计算密集型应用,将计算分解到多个线程中实现

2、进程和线程的区别

1)进程是系统进行资源分配的最小单位,线程是程序执行的最小单位

2)进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈

3)由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行通信

4)线程的创建,切换及终止效率更高

三、常见的锁策略

1、乐观锁 vs 悲观锁

悲观锁:

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都上锁,这样别人想拿到这个数据就会阻塞直到拿到锁

乐观锁:

假设数据一般情况下不会发生冲突,所以在数据提交更新的时候,才会正式对数据是否产生并发进行检测,如果发现并冲突了,则让返回用户错误的信息,让用户决定处理

synchronized 初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换悲观锁策略

?乐观锁的一个重要功能?就是要检测出数据是否发生冲突,我们可以引入一个”版本号“来解决

?

2、读写锁

多线程之间,数据的读取方式之间不会产生线程安全问题,但数据的写入方式互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗,所以读写锁因此而生。

读写锁(readers-writer lock) 在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。

一个线程对于数据的访问,主要存在两种操作:读数据和写数据

1)两个线程都只是读一个数据,此时并没有线程安全问题,直接并发的读取即可

2)两个线程都要写一个数据,此时会出现线程安全问题

3)一个线程读另外一个线程写,也有线程安全问题

读写锁就是把读操作和写操作区分对待。JAVA 标准库中提供了 ReentrantReadWriteLock 类,实现了读写锁

其中,

?读写锁 特别适合于”频繁读,不频繁写“的场景中

比如一个教务系统

synchronized 不是读写锁

3、重量级锁 vs 轻量级锁

锁的核心特性”原子性“,这样的机制追根溯源是 CPU 这样的硬件设备提供的

1)CPU 提供了”原子操作指令“

2)操作系统基于CPU的原子指令,实现了 mutex 互斥锁

3)JVM基于操作系统提供的互斥锁,实现了 synchronized 和 ReentrantLock 等关键字和类

?

重量级锁:加锁机制重度依赖 OS 提供的 mutex

?1)大量的内核态用户态切换

2)很容易引发线程的调度

轻量级锁:加锁机制尽可能不用 mutex,而是尽量在用户态代码完成,实在搞不定再用mutex

1)少量的内核态用户态切换

2)不太容易引发线程调度

4、自旋锁(Spin Lock)

?按之前的方式,线程在抢锁失败后进入阻塞状态,放弃CPU,需要很久才能被再次调度

但实际上,大部分情况下,虽然当前抢锁失败,但过不了多久,锁就会被释放。没必要放弃这个CPU,这个时候就可以使用自旋锁来处理这样的问题

自旋锁伪代码:

while (抢锁(lock)== 失败) {}

如果获取锁失败,立即再尝试获取锁,无限循环,直到获取锁为止,第一次获取锁失败,第二次的尝试会在极短的时间内到来

一旦锁被其他线程释放,就能第一时间获取到锁,

自旋锁是一种典型的 轻量级锁 的实现方式

优点: 没有放弃CPU,不涉及线程阻塞和调度,一旦锁被释放,就能第一时间获取到锁

缺点:如果锁被其他线程持有的时间比较久,那么就会持续的消耗CPU资源(而挂起等待的时候是不消耗CPU的)

synchronized 中的轻量级锁策略大概率是通过自旋锁的方式实现的

5、公平锁 vs 非公平锁

?

1) 操作系统内部的线程调度可以视为随机的,如果不做任何额外的限制,锁就是非公平锁,如果要想实现公平锁,就需要依赖额外的数据结构,来记录线程们的先后顺序

2)公平锁和非公平锁没有好坏之分,关键看适用场景

Synchronized 是非公平锁

6、可重入锁 vs 不可重入锁

可重入锁的字面意思是”可以重新进入的锁“,即允许同一个线程多次获取同一把锁。

比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入锁(因为这个原因可重入锁也叫递归锁)

Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的

而LInux提供的mutex是不可重入锁

面试葵花宝典

1、你是怎么理解乐观锁和悲观锁的,具体怎么实现?

2、介绍下读写锁?

3、什么是自旋锁,为什么要使用自旋锁策略,缺点是什么?

4、synchronized 是可重入锁么?

5、CAS?

1)什么是CAS?

CAS:全称Compare and swap,字面意思:”比较并交换:,一个CAS涉及到以下操作:

?CAS伪代码

下面写的代码不是原子的,真实的 CAS 是一个原子的硬件指令完成的,这个伪代码只是辅助理解CAS 的工作流程

两种典型的不是“原子性”的代码

1)check and set (if 判定然后设定值)

2)read and update(i++)

当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号

CAS 可以视为一种乐观锁(或者理解成 CAS 是乐观锁的一种实现方式)

2)CAS 是怎么实现的

?针对不同的操作系统,JVM用到了不同的CAS实现原理,简单来讲:

1)java 的 CAS 利用的是 unsafe 这个类提供的 CAS 操作

2)unsafe 的CAS 依赖了的是 JVM 针对不同的操作系统实现的 Atomic

简而言之,硬件给予支持,软件层面才能做到

3)CAS有哪些应用?

1)实现原子类

这里边的 getAndIncrement 相当于 i++ 操作?

伪代码实现:

?

?

2)实现自旋锁

基于 CAS 实现更灵活的锁,获取到更多的控制权

4)CAS 的 ABA 问题

1)什么是 ABA 问题?

ABA 问题:

假设存在两个线程 t1 和 t2 ,有一个共享变量 num,初始值为 A

接下来,线程 t1 想使用 CAS 把值改为 Z,那么就需要

1、先读取 num 的值,记录到 oldNum 变量中

2、使用 CAS 判定当前 num 的值是否为 A,如果为 A,就修改为 Z

?2)ABA 问题引来的 BUG

大部分情况下,t2 线程这样的一个反复横跳改动,对于 t1 是否修改 num,是没有影响的,但是不排除一些特殊情况

3)解决 ABA 问题的方案

给要修改的值,引入版本号,在 CAS? 比较数据当前值和旧值之前,也要比较版本号是否符合预期。

?

5)高频面试题

1)讲解下你自己理解的 CAS 原理

CAS 全称 Compare and swap ,“比较并交换”,相当于通过一个原子的操作,同时完成“读取内存,比较是否相等,修改内存”这三个步骤,本质上需要 CPU 指令的支撑

2)ABA 问题怎么解决?

给需要修改的数据引入版本号,在 CAS 比较数据当前值和旧值的同时,也要比较版本号是否符合预期,如果发现版本号一致就真正执行修改操作,并且让版本号自增。如果发现版本号不一致(数据一定被修改过),操作失败。

6)Synchronized 原理

1) 基本特点

?结合上面的锁策略,我们可以总结出,Synchronized 具有以下特性

1、开始是乐观锁,如果频繁的锁冲突,就转换为悲观锁

2、开始是轻量级锁,如果锁被持有的时间过长,就转换为重量级锁

3、实现轻量级锁的时候大概率用到的自旋锁策略

4、是一种不公平锁

5、是一种可重入锁

6、不是读写锁

2)加锁工作过程

JVM 将 synchronized 锁分为 无锁,偏向锁,轻量级锁,重量级锁状态。会根据情况,依次进行升级

7) 偏向锁 -->? 轻量级锁 --> 重量级锁

1、偏向锁

第一个尝试加锁的过程,优先进入偏向锁状态

2、轻量级锁

随着其他线程进入竞争,偏向锁状态被消除,进入轻量级锁状态(自适应的自旋锁)

此处的轻量级锁就是通过 CAS 来实现

3、重量级锁

如果竞争进一步激烈,自旋不能快速获取到锁的状态,就会膨胀为重量级锁

此处的重量级锁就是指内核提供的 mutex

7)锁消除 ?锁粗化?

1、锁消除

编辑器 + JVM 判断锁是否可消除,如果可以,就直接消除

2、锁粗化?

?一段逻辑中如果多次出现加锁解锁,编译器 + JVM 会自动进行锁的粗化

由此可见,synchronized 的策略是比较复杂的,在背后做了很多事情,目的为了让程序员哪怕什么也不懂,也不至于写出特别慢的程序

8)Callable 接口

1、Callable 的用法

Callable 是一个 interface,相当于把线程封装了一个“返回值”,方便程序员借助多线程的方式计算结果

?

public class Result {
    static class Res {
        public int sum;
        public Object lock = new Object();
    }
    public static void main(String[] args) throws InterruptedException {
        Res  res = new Res();
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                int sum = 0;
                for (int i = 0; i < 1000; i ++) {
                    sum++;
                }
                synchronized (res.lock) {
                    res.sum = sum;
                    res.lock.notify();
                }
            }
        });
        t.start();
        synchronized (res.lock) {
            while (res.sum == 0) {
                res.lock.wait();
            }
            System.out.println(res.sum);
        }
    }
}

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Result {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int res = 0;
                for (int i = 0; i < 1000; i ++) {
                    res++;
                }
                return res;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask(callable);
        Thread t = new Thread(futureTask);
        t.start();
        int res = futureTask.get();
        System.out.println(res);
    }
}

?

相关面试题

?9)JUC(java.util.concurrent)的常见类

ReentrantLock

可重入互斥锁,和 synchronized 定位类似,都是用来实现互斥效果,保证线程安全

ReentrantLock 和 synchronized 的区别

?如何选择使用那个锁?

10)原子类

?原子类内部用的是 CAS 实现,所以性能要比加锁实现 i++ 高很多。原子类有以下几个:

11)线程池

虽然创建销毁线程比创建销毁进程更轻量,但是在频繁创建销毁线程的时候还是会比较低效

线程池就是为了解决频繁创建销毁线程的问题,如果某个线程不再使用了,并不是真正把线程释放,而是放到一个“池子”中,下次如果需要用到线程就直接从池子中取,不必通过系统来创建。

?

?

12)信号量

信号量,可以用来表示“可用资源的个数”,本质上就是一个计数器

?

public class Worker {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(4);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("我要申请资源了!");
                    semaphore.acquire();
                    System.out.println("我申请到资源了");
                    Thread.sleep(1000);
                    System.out.println("我释放资源了!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 10; i ++) {
            Thread t = new Thread(runnable);
            t.start();
        }
    }

}

?13)CountDownLatch

同时等待 N 个任务执行结束

14)相关面试题

1、线程同步的方式

synochronized , ReentrantLock ,Semaphore 等都可以用于线程同步

2、为什么有了 synchronized 还需要 juc 下的 lock?

3、AtomicInteger 的实现原理是什么?

?

4、信号量听说过?之前都用在那些场景下?

?

5、解释一下 ThreadPoolExecutor 构造方法的参数含义

参考:

(306条消息) 面试问我线程池?还好我早有应对_yan扬的博客-CSDN博客https://blog.csdn.net/qq_59539549/article/details/125014470?spm=1001.2014.3001.5501

?15)线程安全的集合类

原来的集合类,大部分都不是线程安全的

16)多线程环境使用 ArrayList?

1、自己使用同步机制 (synchronized 或者 ReentrantLock)

2、Collections.synchronizedList(new ArrayList);

?3、使用 CopyOnWriteArrayList

?17)多线程环境使用队列

18)多线程环境使用哈希表

HashMap 本身不是线程安全的

?

?

19)相关面试题

1、ConcurrentHashMap 的读是否要加锁,为什么?

读操作没有加锁,目的是为了进一步降低锁冲突的概率,为了保证读到刚修改的数据,搭配了 volatile 关键字

2、介绍下 ConcurrentHashMap 的锁分段技术?

3、ConcurrentHashMap 在jdk1.8 做了那些优化?

?4、Hashtable 和 HashMap ,ConcurrentHashMap 之间的区别?

20)死锁?

?1、死锁是什么?

死锁的情形:多个线程同时被阻塞,他们中的一个或多个全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止

?

?死锁是一种很严重地 BUG !导致一个程序卡死,无法正常工作

21)如何避免死锁?

死锁产生地四个必要条件:

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放

3、请求和保持,即当资源请求者在请求其他资源的同时保持对原有资源地占有

4、循环等待:即存在一个等待队列:p1 占有 p2 的资源, p2 占有 p3 的资源,p3 占有 p1 的资源,这样就形成了一个等待环路。

?

?破坏循环等待

最常用的一种死锁阻止技术就是”锁排序“,假设有 N 个线程尝试 同时获取 M 把锁,就可以针对 M 把锁进行编号(1,2,3....M)

N 个线程尝试获取锁的时候,都按照固定的编号由小到大顺序来获取锁,这样就可以避免环路等待

22)其他常见问题

1、谈谈 volatile 关键字的用法?

?2、java多线程如何实现数据共享?

3、java 创建线程池的接口是什么?参数 LinkedBlockingQueue 的作用是什么?

4、java 线程共有几种状态?状态之间如何切换?

5、在多线程下,如果对一个数进行叠加,该怎么做?

6、Servlet 是否是线程安全的?

?7、Thread 和 Runable 的区别和联系?

?8、多次 start 一个线程会怎么样?

9、有 synchronized 两个方法,两个线程分别同时用这个方法,请问会发生什么?

10、进程和线程的区别?

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章           查看所有文章
加:2022-06-08 18:52:54  更:2022-06-08 18:57:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 19:22:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码