线程安全面试题
面试题:
1. 你知道线程与进程的区别吗?
- 进程是包含线程的.每个进程至少有⼀个线程存在,即主线程。
- 进程和进程之间不共享内存空间.同⼀个进程的线程之间共享同⼀个内存空间.
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。.
2. 线程的创建方式有几种?
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
- 实现Callable接口,重写call方法
- 创建线程池,利用线程池创建线程
3.Runnable与Callable的区别?
- Callable要实现call(),且有返回值,Runnable要实现的run()但没有返回值
- Callable的call()可以抛出异常,Runnable的run()不能抛出异常
- Callable配合FutrueTask一起使用,通过futureTask,get()方法获取call()的结果
- 两都是描述线程任务的接口
4.JDK提供的线程池有几种?
6种,如下所示:
//1.用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将回收并移除缓存
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//2.创建一个操作无界队列且固定大小线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//3.创建一个操作无界队列且只有一个工作线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//4.创建一个单线程执行器,可以在给定时间后执行或定期执行
ScheduledExecutorService singleThreadScheduledExecutor=Executors.newSingleThreadScheduledExecutor();
//5.创建一个指定大小的线程池,可以在给定时间后执行或定期执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//6.创建一个指定大少(不传入参数,为当煎机器CPU核心数)的线程池,并行地处理任务
Executors.newWorkStealingPool();
5.手动创建线程池时ThreadPoolExcutor有多少个参数,以及各参数的含义?
7个参数
corePoolSize: 核心线程的数量.(类似于正式员⼯,⼀旦录⽤,永不辞退)
maximumPoolSize: 线程池中最大的线程数=核心线程+临时线程的数目.(类似于临时⼯:⼀段时间不⼲活,就被辞退)
keepAliveTime: 临时线程存活的时间
unit: 临时线程存活的时间单位,是秒,分钟,还是其他值
workQueue: 组织(保存)任务的队列
threadFactory: 创建线程的工厂,参与具体的创建线程⼯作.通过不同线程工厂创建出的线程相当于对一些属性进行了不同的初始化设置
RejectedExecutionHandler: 拒绝策略,如果任务量超出线程池的负荷了接下来怎么处理
6.线程池的拒绝策略有哪些?
7.请你描述一下线程池的工作流程?
8.说一下什么是线程安全问题?
线程安全问题是指在多线程环境中,当多个线程同时访问共享数据时,可能会导致数据的不一致或数据污染。具体来说,当一个线程正在访问或修改共享数据时,另一个线程也在访问或修改同一数据,这就可能导致数据冲突
9.怎么解决线程安全问题?
-
使用同步机制: 通过同步代码块或同步方法,确保在同一时间只能有一个线程修改共享数据。这可以通过synchronized关键字实现,它提供了一种锁机制,能够保证同一时刻只有一个线程执行某个方法或某个代码块,从而保证了共享数据的安全性
-
消除共享数据: 尽量避免共享数据,或者确保共享数据不会被修改。如果必须使用共享数据,可以考虑将数据封装在对象内部,通过对象的方法来访问和修改数据,这样可以通过对象的锁机制来保证线程安全。
-
使用线程本地存储: ThreadLocal类提供了一种线程局部变量的概念,每个线程都有自己独立的变量副本,线程间的数据不会相互影响,从而避免了线程安全问题。
-
使用锁机制: Java 提供了多种锁机制,如ReentrantLock,它提供了比synchronized更丰富的锁操作,可以实现更细粒度的锁控制。此外,还有读写锁ReadWriteLock,它允许多个线程同时读取共享数据,但只有一个线程可以修改数据,这样可以提高并发性能。
-
使用原子类: Java 的java.util.concurrent.atomic包提供了一系列原子类,如AtomicIntegerAtomicReference等,它们利用CAS(Compare-And-Swap)操作来保证操作的原子性,从而避免线程安全问题。
-
使用乐观锁: 乐观锁是一种不常用锁的策略,通常通过版本号来实现。每次操作数据时,都会检查版本号是否发生变化,如果没有变化,则更新数据;如果版本号变化了,则说明数据已经被其他线程修改,当前操作需要重新尝试。
10.Synchronized和volatile的作用与区别?
主要区别:
- 可见性: volatile关键字主要用于保证共享变量的内存可见性,而synchronized在效果上实现了可见性,其保证了原子性和有序性
- 原子性: volatile仅保证被修饰变量的读写操作是原子性的,但对于复合操作(如i++),volatile无法保证原子性,因为这是一个复合操作,包括读取、修改、写入三个步骤,并不能保证这三个步骤的连续性。而synchronized可以保证被修饰代码块的原子性
- 适用范围: volatile适用于只有一个线程写,多个线程读的场景,而synchronized适用于需要保证原子性和有序性的临界区代码
- 性能开销: volatile的性能开销较低,而synchronized的性能开销较高,因为它涉及到锁的获取和释放
11.JMM 的特性?
- 线程之间的共享变量存在主内存(MainMemory).
- 每⼀个线程都有自己的"⼯作内存"(WorkingMemory),且线程工作内存之间是隔离的,线程对共享变量的修改线程执行相互感知不到
- 当线程要读取⼀个共享变量的时候,会先把变量从主内存拷贝到⼯作内存,再从⼯作内存读取数据.
- 当线程要修改⼀个共享变量的时候,也会先修改⼯作内存中的副本,再同步回主内存.
- 工作内存是JAVA层面对物理层面的关于程序所使用到了寄存器的抽象
- 如果通过某种方式 让线程之间可以相互通信,称之为内存可见性
12.Synchronized锁升级的过程?
JVM将synchronized锁分为无锁、偏向锁(轻量级锁)、自旋锁、重量级锁状态。会根据情况,进行依次升级。
13.什么是偏向锁,轻量级锁,重量级锁?
-
无锁
不存在锁 -
偏向锁
偏向锁是Java中最轻量级的锁升级策略。 当一个线程获取到锁时,该锁会进入偏向模式,并将获取到锁的线程ID记录下来(做标记)
。接下来,当这个线程再次请求同一个锁时,无需竞争,可以直接获取,(避免了加锁解锁的开销)
。偏向锁本质上相当于"延迟加锁".能不加锁就不加锁,尽量来避免不必要的加锁开销.但是该做的标记还是得做的,否则⽆法区分何时需要真正加锁.这种策略适用于大部分情况下都是由同一个线程持有锁的场景。 -
轻量级锁
当多个线程同时请求同一个锁时,偏向锁就无法满足需求,锁升级到轻量级锁。 轻量级锁使用CAS(Compare and Swap)操作来避免线程的阻塞和唤醒,从而提高并发性能。当线程获取轻量级锁失败时,锁会升级到下一个阶段。
⾃旋操作是⼀直让CPU空转,比较浪费CPU资源.
因此此处的自旋不会⼀直持续进行,而是达到⼀定的时间/重试次数,就不再自旋了.
也就是所谓的"自适应"
-
自旋锁
自旋锁是轻量级锁升级的一种策略。 当线程在获取轻量级锁失败后,它不会立即被挂起,而是会自旋一段时间,不断尝试获取锁。自旋锁的目的是为了避免线程的上下文切换,提高性能
。但如果自旋时间过长或者自旋次数达到一定阈值,仍然没有成功获取锁,那么锁将会升级到下一个阶段。 -
重量级锁
重量级锁是Java中最重量级的锁升级策略。 当自旋锁尝试获取锁的次数达到阈值时,锁会进入重量级模式。重量级锁采用操作系统的互斥锁实现,真正的调用CPU的指令LOCK
14.介绍一个CAS,以及ABA问题?
CAS:全称Compare and swap,字⾯意思:“比较并交换”,⼀个CAS涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
-
比较A与V是否相等。(比较)
-
如果比较相等,将B写入V。(交换)
-
返回操作是否成功。
ABA问题:
假设存在两个线程t1和t2.有一个共享变量value,初始值为A.
接下来,线程t1想使用CAS把value值改成Z,那么就需要
先读取value的值,记录到oldValue变量中.
使用CAS判定当前value的值是否为A,如果为A,就修改成Z.
但是,在t1执行这两个操作之间,t2线程可能把value的值从A改成了B,又从B改成了A
15.ReentranLock和synchronized的区别?
- synchronized是⼀个关键字,是JVM内部实现的(大概率是基于C++实现).ReentrantLock是标准
库的⼀个类,在JVM外实现的(基于Java实现). - synchronized使用时不需要⼿动释放锁.ReentrantLock使用时需要⼿动释放.使用起来更灵活,但
是也容易遗漏unlock. - synchronized在申请锁失败时,会死等.ReentrantLock可以通过trylock的方式等待⼀段时间就放弃.
- synchronized是非公平锁,ReentrantLock默认是非公平锁.可以通过构造方法传入⼀个true开启公平锁模式.实现的过程中有会一个队列来组织排队的线程
- 更强大的唤醒机制.synchronized是通过Object的wait/notify实现等待-唤醒.每次唤醒的是⼀个随机等待的线程.ReentrantLock搭配
Condition类
实现等待-唤醒,可以更精确控制唤醒某个指定的线程.
16.JUC包下的工具类知道哪些?
Java的java.util.concurrent(简称JUC)
原子变量类:
JUC的atomic包下提供了一些原子变量类,这些类使用CAS(Compare-And-Swap)算法来保证数据的原子性。常见的原子变量类包括:
- AtomicBoolean
- AtomicInteger
- AtomicReference
这些类可以确保在多线程环境下对变量的操作是原子的,从而避免了数据竞争
锁和同步工具类:
JUC包中提供了多种锁和同步工具类,用于协调线程之间的操作。常见的类包括:
- ReentrantLock:显式锁,提供了与synchronized相同的互斥性和内存可见性,但提供了更高的灵活性。
- CountDownLatch:闭锁,允许一个或多个线程等待其他线程完成操作。
- Semaphore:信号量,用于控制对共享资源的访问。
- CyclicBarrier:栅栏,允许一组线程互相等待,达到一个共同点后再继续执行
并发容器类:
JUC包中还提供了一些线程安全的容器类,这些类在并发场景下性能优于传统的同步容器。常见的并发容器类包括:
- ConcurrentHashMap:线程安全的哈希表。
- CopyOnWriteArrayList:线程安全的ArrayList实现,适用于读操作远多于写操作的场景
线程池和执行框架:
JUC包中的Executor框架提供了多种线程池实现,用于管理和调度线程。常见的类包括:
- Executors:线程池工厂类,提供了创建各种类型线程池的方法。
- ThreadPoolExecutor:线程池的实现类。
- ForkJoinPool:用于并行执行任务的线程池,适用于分治算法
阻塞队列类:
JUC包中还提供了一些阻塞队列类,用于在多线程环境下安全地传递数据。常见的阻塞队列类包括:
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
17.线程安全的集合类有哪些?
- 第一代线程安全集合类
Vector
、Hashtable
是怎么保证线程安全的:使用synchronized修饰方法
缺点:效率低下 - 第二代线程非安全集合类
ArrayList
、HashMap
线程不安全,但是性能好,用来替代Vector、Hashtable
使用ArrayList、HashMap,需要线程安全怎么办呢?
Collections.synchronizedList(list)
Collections.synchronizedMap(m)
底层使用synchronized代码块锁虽然也是锁住了所有的代码,但是锁在方法里边,
比锁在方法外边性能可以稍有提高。 - 第三代线程安全集合类
在大量并发情况下如何提高集合的效率和安全呢?
java.util.concurrent.*
ConcurrentHashMap
CopyOnWriteArrayList
CopyOnWriteArraySet:注意不是CopyOnWriteHashSet*
底层大都采用Lock锁(1.8的ConcurrentHashMap不使用Lock锁),保证安全的同时,性能也很高。
18.用过ConcurrnetHashMap吗?介绍一下?
19.造成死锁的原因?以及解决办法?
20. 多次start⼀个线程会怎么样
第⼀次调⽤start可以成功调用.
后续再调用start会抛出java.lang.IllegalThreadStateException
异常
21. Java线程共有几种状态?状态之间怎么切换的?
NEW
:表示创建好了一个Java线程对象,安排好了任务,但是还没有启动
RUNNABLE
:运行+就绪的状态,在执行任务时最常见的状态之一,在系统中有对应PCB
BLOCKED
:等待锁的状态,阻塞中的一种
WAITING
:没有等待时间,一直死等,直到被唤醒
TIMED_WAITING
:指定了等待时间的阻塞状态,过时不侯
TERMINATED
:结束,完成状态,PCB已经销毁,但是JAVA线程对象还在