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

Java并发编程面试题总结

目录

线程有哪几种状态?状态如何流转?

创建线程的方式?

多线程有什么应用?

线程池的好处?

线程池的七个参数?

为什么不推荐使用jdk的Executors创建线程池?

线程池的执行流程?

任务拒绝策略有哪些,怎么选择?

线程池的核心线程数和最大线程数怎么设定?

上线后你会关注线程池的哪些指标?

你怎么理解线程安全?

synchronized的原理实现是什么?

synchronized锁升级过程了解吗?

CAS是什么?应用场景?优缺点?

ReentrantLock是什么,和synchronized相比的区别是什么?

AQS是什么?怎么实现的?

乐观锁是什么?悲观锁是什么?应用场景分别是什么?用数据库如何实现乐观锁

JMM是什么?其中原子性、可见性、有序性问题分别是什么?

JMM如何保证的原子性、可见性、有序性?

常考算法:多线程交替打印0-100

常考算法:多线程交替打印ABC

volatile 关键字的作用是什么?

synchronized 和 volatile 有什么区别?

ConcurrentHashMap 是如何实现线程安全的?

什么是 CopyOnWriteArrayList?BlockingQueue 是什么?

什么是原子操作类?

CountDownLatch和 CyclicBarrier 有什么区别?

Semaphore 是什么?

ConcurrentHashMap 的并发度是什么?

CopyOnWriteArrayList 的优缺点是什么?

BlockingQueue 的实现原理是什么?

ConcurrentLinkedQueue 的特点是什么?

原子操作类的原理是什么?

CountDownLatch和 CyclicBarrier 的用途是什么?

Semaphore 的作用是什么?

Exchanger 的作用是什么?

什么是不可变对象(lmmutable Objects)?

Java 中的垃圾回收(GC)有什么目的?

finalize()方法什么时候被调用?

什么是 CAS(Compare And Swap)操作中的 ABA 问题?

线程的上下文切换

run和 start的区别

sleep和wait区别

虚假唤醒,精确唤醒

两阶段终止模式

多线程下的线程安全问题

如何解决线程安全问题

锁消除

park和unpark

死锁的条件

锁饥饿的问题

什么是读写屏障?

synchronized能否保证JMM的三大特性

手写单例模式,懒汉式,饿汉式,双检锁模式

原子累加器的原理?

什么是缓存行的伪共享问题?

什么是享元模式?在哪些地方有应用?

阻塞队列?

常见的线程池有哪些?

如何处理线程池中的异常?

手写线程池,了解AQS源码

并行和并发有什么区别?

线程和进程的区别?

Runnable和 Callable两个接口创建线程有什区别

那如何停止一个正在运行的线程呢?

ReentrantLock的使用方式和底层原理?

synchronized和Lock有什么区别?

出现了死锁,如何进行死锁诊断?

如何控制某一个方法允许并发访问线程的数

如何保证Java程序在多线程的情况下执行的正确性

谈谈你对ThreadLocal的理解

ThreadLocal的底层原理实现

关于 ThreadLocal会导致内存溢出这个事情,了解吗?


线程有哪几种状态?状态如何流转?

img

  • 新建态:刚刚创建的线程,还没有运行过,没有分配任何资源。

  • 就绪态:除了CPU资源没有获得,其他资源都已经就绪了,一旦线程调用了start方法,就进入就绪状态了

  • 阻塞态:当线程暂时无法继续执行,例如正在等待获取一个锁、等待I/O操作完成或者调用了wait()等方法时,它会进入阻塞态

  • 运行态:线程获取CPU时间片并开始执行run方法内的代码,线程实际执行任务。

  • 终止态:线程在run方法执行完毕或者因为某些异常情况提前退出

新建态到就绪态: 当一个线程从创建到调用start方法,线程就从新建态到就绪态了

就绪态到运行态:处于就绪态中的线程等待JVM线程调度程序分配时间片,当分配到CPU的时间片时,就占用CPU资源,进入运行态

运行态到就绪态:线程时间片用完,或者有更高级别的线程需要运行,那么线程就进入就绪态

运行态到阻塞态:线程运行中会因为某些操作进入阻塞态,比如线程休眠、线程调用wait方法进入等待队列、等待锁、等I/O操作完成等等。

阻塞态到就绪态:导致线程阻塞的原因消失,线程就从阻塞态到就绪态了,比如说获得了锁、被唤醒等

运行态到终止态:线程在run方法执行完毕或者因为某些异常情况提前退出,线程就进入终止态了

创建线程的方式?

1.继承Thread类,重新run方法

2.Runnable实现接口中的run方法

3.Future和Callable结合,可以创建带返回值的线程

多线程有什么应用?
  • 服务器端程序通常需要同时处理来自多个客户端的请求。通过为每个新连接创建一个独立的线程来处理,可以实现高并发处理能力,提高服务器的吞吐量和服务质量。

  • 图形用户界面(GUI)应用程序常常使用多线程来保持用户界面的响应性。

  • 数据库管理系统利用多线程技术来同时处理多个查询请求,优化查询执行计划,以及维护事务的一致性和隔离性。

线程池的好处?
  • 不用频繁创建线程,一定程度上避免了因为频繁创建和销毁线程带来的性能降低。

  • 可以控制线程并发的数量,防止因为线程过多而导致系统耗尽资源。

  • 线程池通过拒绝策略进行优雅的错误控制

线程池的七个参数?
  • corePoolSize:核心线程数(最少线程数)

  • maximumPoolSize:最大线程数

  • unit:时间单位

  • workQueue:当所有的核心线程都在忙碌,新添加的任务会被分配到工作队列中

  • keepAliveTime:当线程数大于核心线程时,空闲的线程的存活时间

  • threadFactory:定义自定义创建线程的方法

  • handler:拒绝策略,当线程池和任务队列都满的时候,对于新提交的任务采取的策略。

为什么不推荐使用jdk的Executors创建线程池?
  • jdk的Executors预定义的线程池设定的是固定大小或者默认大小的线程池,所以线程池大小可能不合适。

  • Executors提供的预定义的线程池采用无界队列,有可能会造成OOM,内存溢出

  • 没有拒绝策略和自定义的ThreadFactory

线程池的执行流程?
  • 任务提交:创建任务并submit到线程池中

  • 任务分配:如果当前运行的线程数小于核心线程数,那么就会分配一个核心线程给当前任务执行;如果当前运行的线程数大于核心线程数并且小于最大线程数时,就会创建一个新的线程分配给当前任务;当当前线程池已满,那么提交的任务就会被放在任务队列;当任务队列和线程池都满了,会采用拒绝策略去处理这些新提交的任务,空闲的线程会从任务队列中取出任务执行。

  • 线程回收:大于核心线程的空闲线程,在存活时间到达的时候会被销毁

任务拒绝策略有哪些,怎么选择?
  • AbortPolicy:直接抛出异常RejectedExecutionException

  • CallerRunsPolicy:由提交任务的线程执行该任务

  • DiscardPolicy:拒绝新任务

  • DiscardOldestPolicy:抛弃任务队列中最老的那一个,将新任务添加进去

线程池的核心线程数和最大线程数怎么设定?

核心线程数:

  • CPU密集型任务:如果应用程序要进行大量的计算,核心线程数一般设置为处理器的内核数量相同或略少

  • I/O密集型任务:如果涉及大量I/O操作的任务,由于线程在等待I/O操作完成期间处于阻塞状态,因此可以设置更高的核心线程数。

最大线程数:

  • 应对突发流量:最大线程数决定了在高负载情况下线程池能够扩展到的最大规模

  • 结合队列策略考虑:如果你使用的是有界队列,则可以根据队列大小调整最大线程数;如果你使用的是无界队列,则不需要过多的线程最大线程数,因为所有的任务都可以在任务队列中等待处理。

上线后你会关注线程池的哪些指标?
  • 活跃线程数

  • 队列大小

  • 已完成的任务

  • 拒绝的任务

  • 线程池的利用率

  • 线程创建和销毁的频率

  • 平均任务执行时间

  • 线程空闲时间

  • 异常发生率

  • 内存使用状况:使用无界队列的时候需要注意

你怎么理解线程安全?

线程安全指的是一个函数、方法或代码块可以在多线程环境下正确执行,不会因为线程并发访问导致数据不一致的情况。

synchronized的原理实现是什么?

Monitor锁:在JVM中,每一个对象都有对应的与之关联的监视器,当一个线程尝试进入sychronized代码块中时都需要占有对应的锁对象。在字节码指令中是通过合适的monitorenter和monitorexit的字节码指令获取锁和释放锁。

synchronized锁升级过程了解吗?
  • 偏向锁:当线程第一次进入同步代码块中,会尝试获取偏向锁,并将线程id记录在对象头中。

  • 轻量级锁:如果有其他线程试图获取该对象的偏向锁,那么偏向锁会升级成轻量级锁。轻量锁通过CAS操作尝试原子性地获得锁,如果成功就进入同步代码块;不成功,轻量级锁可能会升级成重量级锁。

  • 重量级锁:当锁竞争加剧,JVM会将轻量级锁升级为重量级锁,在重量级锁的状况下,所有试图获取该锁的对象都会阻塞,直到拥有该锁的线程释放锁

CAS是什么?应用场景?优缺点?

CAS是硬件系统提供的原子操作,提供三个参数,内存地址,预期值,新值。用于检查一个内存位置的值是否等于预期值,如果是,则用新值替换旧值。这是实现无锁算法最常用的原子操作。

ReentrantLock是什么,和synchronized相比的区别是什么?

ReentrantLock是Java中提供的一种灵活、可重入的显式锁,支持尝试锁定、公平性选择及更复杂的线程同步操作,相比synchronized提供了更高的灵活性和控制能力。Reentrantlock中提供await/Condition,允许线程进入不同的等待队列中;sychcronized所有等待同一把锁的线程会进入全局等待队列中。

AQS是什么?怎么实现的?

AQS原理

  • AQS全称AbstractQueuedSychronizer,是抽象的队列式同步器,定义了一套多线程访问共享资源的同步器框架,许多我们使用的同步器都是基于AQS实现的,如Reentrantlock、Semaphore、CountDownLatch、CyclicBarrie并发类。

  • AQS基于state(同步状态)和CHL队列(FIFO双向队列)。如果当前线程获取同步状态失败,那么AQS就会将线程以及状态等信息构造一个Node节点,并将这个节点添加至队尾,同时阻塞当前线程;当同步状态释放,唤醒队头线程。

乐观锁是什么?悲观锁是什么?应用场景分别是什么?用数据库如何实现乐观锁

悲观锁和乐观锁都是处理并发控制的策略,主要用来解决多进程或多线程并发访问资源时或出现的安全问题

  • 悲观锁:认为线程安全问题一定会发生,所以每次操作前都会加锁,如sychronized和ReentrantLock都是悲观锁。

  • 乐观锁:认为线程安全问题不一定会发生,一般采用CAS的方式来控制线程安全问题。

  • 应用场景:悲观锁适用于写操作频繁且冲突可能性较大的场景,如某些金融交易系统;乐观锁适用于读多写少的场景,如在线阅读。

  • 数据库实现乐观锁:

  • 假如需要更新数据库的库存字段时,会给库存表添加一个字段version代表版本号,每次在进行更新库存前会查询版本号,然后在尝试更新时检查这个版本号是否发生了变化。

JMM是什么?其中原子性、可见性、有序性问题分别是什么?

JMM是JVM内存模型,是Java语言规范的一部分,它定义了程序中各个线程如何通过内存来进行通信的规则,确保了多线程环境下各个线程对共享资源有一定的可见性和有序性。

原子性:指操作不可分割,要么全都执行,要么都不执行。

可见性:一个线程对共享变量所做的修改需要对其他线程变得可见。

有序性:Java允许编译器和处理器对指令重排序,只要这种重排序不改变程序在单线程下的行为。

JMM如何保证的原子性、可见性、有序性?
  • 原子性:对于简单的读取和写入(除long和double外),JMM已经确保其原子性。对于复合操作,可以使用同步机制(如synchronized关键字)、显式锁(如ReentrantLock类)或者原子变量类(如AtomicInteger)来保证原子性。

  • 可见性:volatile关键字保证了变量的原子性,保证每次读取变量都是从主存中读取最新的值。sychronized和显示锁,在进入sychronized代码块或获得锁时都会刷新缓存,每次退出sychronized代码块或释放锁时都会将缓存的数据刷新回主存。

  • 有序性:规定了先行先发生原则,用来确定那些指令重排序是能够被接受的。volitaile关键字保证了禁止指令重排序。

常考算法:多线程交替打印0-100

sychronized:

// 多线程打印0-100
@Slf4j(topic = "c.Test3")
public class Test3 {static Object lock = new Object();static int cnt = 0;static class NumberPrinter implements Runnable{private final int reminder;public NumberPrinter(int reminder) {this.reminder = reminder;}
​@Overridepublic void run() {while (cnt <= 100) {synchronized (lock) {if (cnt % 3 == reminder) {log.debug("{}", cnt);cnt ++;lock.notifyAll();}else {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}public static void main(String[] args) {NumberPrinter task0 = new NumberPrinter(0);NumberPrinter task1 = new NumberPrinter(1);NumberPrinter task2 = new NumberPrinter(2);
​new Thread(task0,"t0").start();new Thread(task1, "t1").start();new Thread(task2, "t2").start();}
}
 

ReentrantLock:

public class Test4 {static ReentrantLock lock = new ReentrantLock();static Condition[] conditions = new Condition[3];static int cnt = 0;static {for (int i = 0; i < 3; i ++){conditions[i] = lock.newCondition();}}
​static class NumberPrinter implements Runnable{private final int reminder;public NumberPrinter(int reminder) {this.reminder = reminder;}
​@Overridepublic void run() {while (cnt <= 100) {lock.lock();if (cnt % 3 != reminder) {try {conditions[reminder].await();} catch (InterruptedException e) {e.printStackTrace();}}else {System.out.println(Thread.currentThread().getName() + ": " + cnt);cnt ++;conditions[cnt % 3].signal();}}}}public static void main(String[] args) {Test3.NumberPrinter task0 = new Test3.NumberPrinter(0);Test3.NumberPrinter task1 = new Test3.NumberPrinter(1);Test3.NumberPrinter task2 = new Test3.NumberPrinter(2);
​new Thread(task0,"t0").start();new Thread(task1, "t1").start();new Thread(task2, "t2").start();}
}
​

常考算法:多线程交替打印ABC
public class ABCPrint {static ReentrantLock lock = new ReentrantLock();static int cnt = 0;static int c = 0;static Condition[] conditions = new Condition[3];static {for (int i = 0; i < 3; i ++) {conditions[i] = lock.newCondition();}}
​static class ABCPrinter implements Runnable {private final int reminder;public ABCPrinter(int reminder) {this.reminder = reminder;}@Overridepublic void run() {while (cnt <= 100) {lock.lock();if (cnt % 3 != reminder) {try {conditions[reminder].await();} catch (InterruptedException e) {e.printStackTrace();}} else {char x = (char) ((char)c + 'A');System.out.print(x);cnt ++;c = (c + 1) % 3;conditions[cnt % 3].signal();}}}}public static void main(String[] args) {ABCPrinter task0 = new ABCPrinter(0);ABCPrinter task1 = new ABCPrinter(1);ABCPrinter task2 = new ABCPrinter(2);
​new Thread(task0,"t0").start();new Thread(task1, "t1").start();new Thread(task2, "t2").start();}
}

volatile 关键字的作用是什么?

保证可见性和禁止指令重排序。

  • 可见性:volatile修饰的变量,保证每次写入会被立即刷新到主存中,每次读取都在主存中读取

  • 禁止指令重排序:防止因为指令重排序造成的并发安全问题。volatile变量之前的和之后的普通变量读写操作不会被重排序越过volatile变量的操作

synchronized 和 volatile 有什么区别?
  • sychronized是修饰方法、代码块,提供原子性保证、可见性,遵循”先行先发生“原则。进入sychronized代码块时会刷新缓存,退出时刷新主存;sychronized可以解决多线程并发问题,当多个线程想要获取同一个对象的Monitor锁时,只有一个线程能进入,并且代码块内的操作是原子性的,只有该线程退出代码块,其他线程才能占有锁;遵循”先行先发生“原则,同步块内的操作不会被重排序到同步块以外。

  • volatile修饰变量,不提供原子性保证,保证有序性和禁止指令重排序。

ConcurrentHashMap 是如何实现线程安全的?
  • 在jdk1.7中 ,ConcurrentHashMap采用分段锁技术。整个哈希表被分割成多个小的哈希表(Segment),每个 Segment 都是一个独立的锁。这意味着不同的线程可以同时对不同的 Segment 进行读写操作,从而大大提高了并发度。

  • 在jdk1.8中,取消了分段锁,改用CAS和Sychronized关键字来控制并发,当某个桶的节点超过阈值8,则由链表转为平衡二叉树(红黑树),扩容机制也进行了优化,旧桶中的元素会被分散到新的桶中。

什么是 CopyOnWriteArrayList?BlockingQueue 是什么?
  • CopyOnWriteArrayList是JUC包下的一个线程安全的集合类,主要用于读多写少的场景。在进行写操作时,底层会创建一个全新副本进行操作;在进行读操作时,不会进行加锁,而是直接返回数组快照。

  • BlockingQueue是JUC包下的一个接口,提供两个附加操作,在队列为空时阻塞获取,队列满时阻塞插入。是一种高效的线程间通信机制。

什么是原子操作类?

是JUC提供的无锁、线程安全的操作类。允许对单个变量进行非阻塞式的原子操作,在高并发环境下实现高效的数据访问和修改,底层通过硬件指令如CAS操作来保证操作的原子性。

CountDownLatch和 CyclicBarrier 有什么区别?

都是JUC包下的同步辅助类,用于协商多个线程之间的操作。

  • CountDownLatch允许一个或多个线程等到,直到其他线程完成一系列操作,只能使用一次

  • CyclicBarrier 允许多组线程互相等待直到达到共同的屏障点,支持复用且每次屏障触发时可以执行自定义动作

Semaphore 是什么?

Semaphore是juc包下提供的一个同步工具类,用于控制同时访问某一资源或资源池的线程数量,Semaphore通过维护一定数量的许可来实现这点,如果要获取访问权限,就必须先获取一个许可,这会减少许可证的数量;当许可证不可用时(许可用完了),此时请求的线程会阻塞,直到有许可被释放。

ConcurrentHashMap 的并发度是什么?

ConcurrentHashMap的并发度指的是可以同时并发执行更新操作的线程数量。在jdk1.7中,这里的并发度一般指的是分段数量,分段默认是16个,所以允许最多16个线程可以同时对ConcurrentHashMap的不同部分进行修改而不发生锁竞争;在jdk1.8中,取消了分段锁,并发度更多依赖数据结构内部和利用多核处理器的能力进行无锁或低锁争用的操作。

CopyOnWriteArrayList 的优缺点是什么?
  • 每次写操作都会创建副本进行操作,在频繁写操作时可能会造成内存浪费和性能降低。

  • 在读多写少的场景下,系统性能提升

BlockingQueue 的实现原理是什么?

BlockingQueue 的各种实现主要依赖于 Java 的并发工具包中的锁机制(如 ReentrantLock)、条件变量(Condition)以及特定的数据结构(如数组、链表、堆等)。这些机制共同作用,确保了即使在多线程环境中也能正确地处理队列的插入、删除和查询操作,同时还能有效地处理当队列满或空时的情况,即阻塞相应的操作直到条件满足为止。

ConcurrentLinkedQueue 的特点是什么?

ConcurrentLinkedQueue 是juc包下提供的一个无界非阻塞队列,基于链表实现,遵循先进先出原则,适用于高并发环境下的高效操作。

原子操作类的原理是什么?

底层通过CAS在每次更新前都会去比较预期值和当前值,保证在更新过程中没有其他线程来修改当前值,从而保证了原子性。

CountDownLatch和 CyclicBarrier 的用途是什么?

都是JUC包下的同步辅助类,用于协商多个线程之间的操作。

  • CountDownLatch:适用于一次性事件,通常用于那些有明确起始点和结束点的任务集合。如确保一组任务在执行前多有资源都初始化完成或等待所有子任务执行完进行汇总计算。

  • CyclicBarrier :适合于需要周期性同步的一组线程,它可以重复使用,并提供了在每次同步点触发时执行自定义逻辑的能力。这可用于在每次同步时执行一些合并或总结操作。如并发迭代算法中,每一次迭代都需要参与线程同步进度;多玩家回合制游戏中,每个玩家需要等待其他玩家在本回合结束才能进入下一个回合

Semaphore 的作用是什么?
  • Semaphore用于控制多线程对某个资源或资源池的并发访问。通过许可来实现,每个线程获取许可就获取了许可,就可以访问资源,许可数量减少,当许可不可用(许可数量为0)时,当前请求资源的线程被阻塞。

  • Semaphore可以作为并发工具,协调线程操作顺序。

Exchanger 的作用是什么?

Exchanger是juc包下的一个并发工具类,用于两个线程之间的交换数据。Exchanger提供同步点,两个线程通过同步点进行交换数据(通常是对象)

什么是不可变对象(lmmutable Objects)?

不可变对象指的是一旦被创建就不能修改了。有以下几个特点:

1.所有字段都被设置为final并且不提供方法修改

2.保证内部可变组件不被篡改

3.因为不可变,所以可以使用hashCode缓存

Java 中的垃圾回收(GC)有什么目的?

主要目的是管理和回收不再使用的对象所占用的内存,以避免内存溢出和资源浪费,并且通过有效地管理内存来提升应用程序的整体性能

finalize()方法什么时候被调用?

旨在给对象一个机会在垃圾回收器回收该对象所占用的内存之前执行清理操作。然而,使用 finalize() 方法进行资源管理已经被认为是不推荐的做法,并且在现代Java编程实践中很少被使用。

什么是 CAS(Compare And Swap)操作中的 ABA 问题?

ABA问题:

1.T1线程读到V变量的值A(0),然后准备通过CAS操作把A变成B

2.T2线程读到V变量的值A(0),把A(0)变成B(2),又改回了A(2)

3.T1执行CAS操作时,会发现这个A(2)已经不是最初的A(0)了,但是还是继续执行,把V值从A(2)变成 B(1)

从技术角度看,CAS 操作成功了,因为它确实实现了预期的状态转换(从 A 到 B),但从逻辑上讲,这可能并不是我们期望的结果,因为我们通常希望确保的是“V 从未被其他线程改变过”,而不是仅仅“V 当前的值与我上次看到的一样”。

线程的上下文切换

cpu给线程的时间片用完了就会引起线程的上下文切换。当一个线程的时间片用完时,CPU 会暂停该线程的执行,并保存其当前状态(即上下文),以便稍后可以恢复执行。接着,CPU 会选择另一个线程来执行,加载其上下文并继续执行。

run和 start的区别
  • run 方法:这是在实现 Runnable 接口时需要重写的方法,或者是在扩展 Thread 类时覆盖的方法。run 方法包含了线程执行的代码逻辑。直接调用 run 方法实际上是在当前线程中同步执行这段代码,并不会启动一个新的线程。

  • start 方法:调用 start 方法会创建并启动一个新的线程,在这个新线程中执行 run 方法中的代码。这意味着你可以利用多核处理器的优势,同时运行多个任务。

sleep和wait区别
  • sleep:线程会进入TIME_WAITING状态,进入休眠,但不会释放任何锁(如果有的话)。也就是说,其他线程不能访问被当前线程持有的锁保护的资源,直到被中断或指定时间过去。

  • wait:线程获得了锁,当调用wait时,线程会进入WAITING或者TIME_WAITING状态,当前线程会加入到对象的waitSet集合中,会自动释放当前线程持有的对象锁,允许其他线程进入同步块或方法。进入waitSet集合的线程等待被唤醒才能继续执行。

虚假唤醒,精确唤醒
  • 虚假唤醒:某个线程在没有调用notify或notifyAll的情况下从wait状态醒来。

  • 精确唤醒:线程通过notify或notifyAll被唤醒

两阶段终止模式

是一种设计模式,用于优雅地终止线程。解决了直接释放线程而导致的数据不一致和资源未释放等问题。通过两个明确阶段确保线程安全退出:通知终止和实际终止。

  • 通知终止:主线程或者其他管理线程通过某种机制通知目标线程终止,目标线程在运行过程中定期检查是否收到了终止通知,并做好准备清理工作。

  • 实际终止:当目标线程完成当前任务或者到达一个合适的暂停点时,将执行必要的清理操作,然后正常退出。

多线程下的线程安全问题
  • 竞态条件:当两个或多个线程尝试同时访问和修改同一个共享资源(如变量、数据结构等),且至少有一个线程执行写操作时,如果没有适当的同步机制来控制这些访问的顺序,就可能发生竞态条件。这会导致不确定的结果或程序错误。

  • 原子性问题:某些操作不可分割,要么全都执行,要么都不执行。如果这些操作被中断或交错执行就可能导致数据不一致。

  • 死锁:死锁(Deadlock)是指两个或更多线程在执行过程中,由于争夺资源而造成的一种互相等待的状态。

  • 活锁:在活锁的情况下,线程不断地尝试执行某项操作但总是失败,并且不断地重复相同的操作,导致它们实际上没有取得任何进展

  • 饥饿:“饥饿”(Starvation)是指一个线程由于无法获得所需的资源而无法继续执行的情况。尽管该线程没有被阻塞,但由于其他线程频繁地抢占资源,导致这个线程得不到足够的CPU时间或其他必要的资源来推进其任务

如何解决线程安全问题
  • 使用同步机制:Sychronized关键字和ReentrantLock显式锁

  • 使用原子变量:Atomic 类型,提供无锁的原子操作。

  • 使用并发集合:ConcurrentHashMap, CopyOnWriteArrayList

  • 使用信号量(Semaphore):可用于控制同时访问某一特定资源或资源池的线程数量

  • 使用阻塞队列:是一种线程安全的队列。队列为空时阻塞读取,队列满时阻塞插入

  • 使用线程局部变量(ThreadLocal):为每个线程提供独立的变量副本,使得每个线程都可以独立操作而不影响其他线程的状态

锁消除

JVM提供的一种锁优化技术,用来自动识别并移除那些不必要的同步代码块,以减少锁的开销并提高程序的执行效率。这种优化基于逃逸分析,它是一种编译器技术,用来判断方法的作用域是否超出了方法或线程的范围。

park和unpark

park和unpark是juc提供的低阻塞机制和接触阻塞的机制,它们由LockSupport提供。每次调用 unpark 都会给目标线程发放一个许可,而每次调用 park 都会消耗掉一个许可。如果在没有任何许可的情况下调用 park,则该线程会被阻塞,直到有新的许可发放或出现中断等情况。

  • park:挂起当前线程,进入等待状态,直到另一个线程调用 unpark 方法唤醒它

  • unpark:唤醒一个之前被 park 调用挂起的线程。

死锁的条件

两个及以上的线程互相争夺资源而导致的互相等待的情况。

四个必要条件:

1.互斥条件

2.占用并等待条件

3.不剥夺条件

4.循环等待条件

锁饥饿的问题

指的是某些线程由于其他线程频繁获取锁而长期得不到执行机会,无法获得所需的锁以继续执行其任务。

原因:

1.非公平锁

2.优先级反转

3.长时间持有锁

4.复杂的锁依赖关系

什么是读写屏障?

主要用于确保在多线程环境下程序的正确性和一致性。它们是编译器和CPU用来控制指令重排序以及保证内存可见性的机制

内存屏障是一种硬件或软件上的指令,用于防止特定类型的编译器优化和处理器乱序执行对程序逻辑的影响。内存屏障可以确保某些操作在屏障前发生的所有操作都已完成,并且对其他处理器核心可见,然后才允许后续的操作开始。

读写屏障是内存屏障的一种具体形式,主要分为两类:读屏障和写屏障

synchronized能否保证JMM的三大特性

Sychronized可以保证原子性、可见性和有序性。

  • 原子性:Sychronized代码块中的内容是原子性的

  • 可见性:进入Sychronized代码块时会刷新缓存,释放锁时会刷新主存。

  • 有序性:遵循"先行先发生"原则

手写单例模式,懒汉式,饿汉式,双检锁模式

Java中实现单例模式有多种方式,包括懒汉式(Lazy Initialization)、饿汉式(Eager Initialization)和双检锁模式(Double-Checked Locking)。

懒汉式:实例只有在第一次使用的时候才会创建,在多线程环境下可能会导致多个实例被创建的问题。

饿汉式:类加载的时候就创建实例,在多线程环境中是线程安全的,并且没有延迟加载的优势

双检锁模式:结合了懒汉式和饿汉式的优点,提供一种既支持延迟加载又能在多线程环境下高效工作的解决方案。它通过双重检查来减少同步开销。

  • 第一次检查:在进入同步块之前进行。如果实例已经创建,则直接返回实例,不需要进入同步块,这减少了获取锁的开销。

  • 第二次检查:在同步块内部进行。这是为了防止多个线程同时通过第一次检查后都尝试创建实例的情况。

原子累加器的原理?

原子累加器通常指的是使用了硬件级别的原子操作(如 Compare-And-Swap, CAS)来实现线程安全的数值增加。在Java中,AtomicInteger, AtomicLong 等类提供了原子操作的支持。这些类通过底层的硬件支持(如CPU指令集中的CAS操作)来保证操作的原子性,从而避免了传统的锁机制带来的性能开销。

什么是缓存行的伪共享问题?

伪共享(False Sharing)是指不同的线程频繁访问不同变量但这些变量位于同一个缓存行内,导致即使这些变量本身没有被共享,但由于它们在同一缓存行上,使得各个处理器缓存的数据不断失效和刷新,影响性能。

什么是享元模式?在哪些地方有应用?

享元模式(Flyweight Pattern)是一种结构型设计模式,旨在最小化内存使用量,通过共享尽可能多的数据来支持大量细粒度的对象。它通过将对象分为内部状态(存储于对象内部且不会随环境改变而变化)和外部状态(随环境改变而变化,存储于对象之外)来减少对象数量。

  • 字体库管理:多个文本可以使用相同的字体样式。

  • 编辑器字符处理:对于文档中的相同字符,可以共享相同的字符对象以节省内存。

阻塞队列?

阻塞队列是一种特殊的队列,在试图向队列中添加元素但队列已满时会阻塞插入方法,直到队列中有空位为止;同样地,当试图从队列中移除元素但队列为空时会阻塞移除方法,直到队列中有可用元素。常见的阻塞队列有 ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue 等

常见的线程池有哪些?
  • FixedThreadPool:固定大小的线程池,适用于负载较重的服务场景。

  • CachedThreadPool:根据需要创建新线程,但在之前构造的线程可用时将重用它们,适合执行大量耗时较短的任务。

  • SingleThreadExecutor:单个后台线程,保证所有任务按顺序执行。

  • ScheduledThreadPool:支持定时及周期性任务执行。

如何处理线程池中的异常?

在使用 ThreadPoolExecutor 提交任务时,默认情况下如果任务抛出未捕获的异常,它会被静默忽略。为了处理这种情况,可以通过以下方式:

自定义 ThreadFactory:在创建线程时设置 UncaughtExceptionHandler。 Future.get():对于通过 submit() 方法提交的任务,可以捕获 Future.get() 抛出的 ExecutionException 来处理异常。

手写线程池,了解AQS源码

手写线程池涉及对任务调度、线程管理等复杂机制的理解。而AbstractQueuedSynchronizer (AQS) 是Java并发包的基础框架,用于实现锁和其他同步器组件。

并行和并发有什么区别?
  • 并行:指的是多个计算或多个程序段在同一时刻同时执行,通常需要多核处理器的支持。

  • 并发:指的是多个计算或多个程序段在一段时间内交替执行,可能是在单核或多核处理器上通过时间片轮转等方式实现。

线程和进程的区别?
  • 进程:是操作系统进行资源分配的基本单位,每个进程拥有独立的内存空间。

  • 线程:是进程中能够独立运行的一个单元,共享所属进程的资源(如内存地址空间),因此线程间通信比进程间通信更高效。

Runnable和 Callable两个接口创建线程有什区别
  • Runnable:实现 Runnable 接口的类只能定义一个 run() 方法,该方法不返回任何结果(即返回类型为 void),并且不能抛出受检异常(checked exceptions)。

  • Callable:实现 Callable 接口可以定义一个 call() 方法,它可以返回结果(泛型类型指定返回值类型),并且能够抛出异常。通常与 Future 或 CompletableFuture 一起使用来获取异步任务的结果。

那如何停止一个正在运行的线程呢?

直接强制停止线程(如调用 stop() 方法)是不推荐的,因为它可能导致资源未正确释放等问题。推荐的方法是设置标志位或使用中断机制。

ReentrantLock的使用方式和底层原理?

基于AQS(AbstractQueuedSynchronizer),通过状态变量管理锁的状态,并维护一个等待队列来处理竞争锁的线程。

synchronized和Lock有什么区别?

synchronized:语法简洁,自动解锁,适用于简单的同步需求。 Lock:提供更灵活的锁定机制,如尝试锁定、可中断锁定、定时锁定等,适用于复杂的同步需求。

出现了死锁,如何进行死锁诊断?
  • JConsole 或 VisualVM:这些工具可以帮助检测Java应用程序中的死锁情况。

  • ThreadMXBean:通过编程方式检查死锁。

如何控制某一个方法允许并发访问线程的数

可以通过 Semaphore 实现

如何保证Java程序在多线程的情况下执行的正确性

使用适当的同步机制(如 synchronized、显式锁、原子变量等),合理设计数据结构以减少共享状态,利用不可变对象等技术手段。

谈谈你对ThreadLocal的理解

ThreadLocal 提供了线程本地变量的功能,每个线程都可以独立地改变其副本,而不会影响其他线程的副本。适用于避免同步开销的情况。

ThreadLocal的底层原理实现

每个 Thread 对象都有一个 ThreadLocalMap 成员变量,用于存储 ThreadLocal 变量的副本。ThreadLocal 的 get() 和 set() 方法操作的就是这个映射表。

关于 ThreadLocal会导致内存溢出这个事情,了解吗?

由于 ThreadLocal 存储在线程的局部变量中,如果线程长期存活且不断向 ThreadLocal 中添加大量数据,可能会导致内存泄漏。特别是在使用线程池的情况下,因为线程池中的线程是复用的,旧的 ThreadLocal 数据如果没有被清理,可能会积累起来,最终导致内存溢出。因此,在不需要使用 ThreadLocal 变量时,应该及时清除它们的数据。

注意:图片来自网络,侵删 

http://www.xdnf.cn/news/6481.html

相关文章:

  • 从管理痛点破局:安科瑞预付费系统赋能高校智慧水电
  • 最优化方法Python计算:有约束优化应用——线性不可分问题支持向量机
  • Java集合框架
  • Python解析Excel入库如何做到行的拆分
  • mysql 基础复习-安装部署、增删改查 、视图、触发器、存储过程、索引、备份恢复迁移、分库分表
  • 五件应该被禁止自行托管的事情(5 Things That Should Be Illegal to Self Host)
  • 【MySQL】基础知识
  • 线程的两种实现方式
  • 云服务模型深度解析:IaaS、PaaS 和 SaaS
  • DevExpressWinForms-TreeList-基础概念介绍
  • 《Java 大视界——Java 大数据在智能电网分布式能源协同调度中的应用与挑战》
  • 面试题:请解释Java中的垃圾回收机制(Garbage Collection, GC),并讨论不同的垃圾回收算法及其优缺点
  • 涨薪技术|0到1学会性能测试第65课-SQL捕获阻塞事件
  • HashSet
  • python打卡打印26
  • Github 2025-05-15 Go开源项目日报 Top10
  • 基于IBM BAW的Case Management进行项目管理示例
  • 单物理机上部署多个TaskManager与调优 Flink 集群
  • 【GESP】C++三级模拟题 luogu-B3849 [GESP样题 三级] 进制转换
  • MCP Server On FC 之旅2: 从 0 到 1 - MCP Server 市场构建与存量 OpenAPI 转 MCP Server
  • AWS Elastic Beanstalk控制台部署Spring极简工程
  • 小刚说C语言刷题—1088求两个数M和N的最大公约数
  • 动态规划-状态压缩DP
  • Spring 框架 JDBC 模板技术详解
  • Apache JMeter API 接口压测技术指南
  • Kafka如何实现高性能
  • 2025长三角杯数学建模C题思路分析:遇见“六小龙
  • VSCode CMake Debug
  • 【docker】--数据卷挂载
  • Unity3D开发AI桌面精灵/宠物系列 【六】 人物模型 语音口型同步 LipSync 、梅尔频谱MFCC技术、支持中英文自定义编辑- 基于 C# 语言开发