Java-多线程面试问题汇总
Java: Java是一个支持高并发、基于类和面向对象的计算机编程语言。
面向对象编程有很多重要的特性,比如:封装,继承,多态和抽象。
- 封装:封装给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其他对象访问的方法来改变它内部的数据。在Java当中,有3种修饰符:public,private和protected。每一种修饰符给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限。
- 继承:继承给对象提供了从基类获取字段和方法的能力。继承提供了代码的重用行,也可以在不修改类的情况下给现存的类添加新特性。
- 多态:多态是编程语言给不同的底层数据类型做相同的接口展示的一种能力。一个多态类型上的操作可以应用到其他类型的值上面。
- 抽象:抽象技术的主要目的是把类的行为和实现细节分离开。
抽象和封装的不同点:
抽象和封装是互补的概念。一方面,抽象关注对象的行为。另一方面,封装关注对象行为的细节。一般是通过隐藏对象内部状态信息做到封装,因此,封装可以看成是用来提供抽象的一种策略。
常见问题:
进程和线程的区别是什么?
- 进程是执行着的应用程序,而线程是进程内部的一个执行序列。
- 一个进程可以有多个线程。线程又叫做轻量级进程。
- 一个程序至少有一个进程,一个进程至少有一 个线程。
- 线程的划分尺度小于进程,使得多线程程序的并发性 高。
- 进程在执行过程中拥有独立的内存单元,而多个 线程共享内存,从而极大地提高了程序的运行效率。
- 每个独立的线 程有一个程序运行的入口、顺序执行序列和程序的出口。但 是线程不能够独立执行,必须依存在应用程序中,由应用程 序提供多个线程执行控制。
- 多线程的意义在于一个应用程序中, 有多个执行部分可以同时执行。但操作系统并没有将多个线 程看做多个独立的应用,来实现进程的调度和管理以及资源 分配。
创建线程有几种不同的方式?
- 继承Thread类,重写run()方法,调用start()方法启动线程
- 实现Runnable接口,重写run()方法,创建Runnable接口实现类的对象,将Runnable接口类对象作为Thread类的参数。
- 使用Callable和Future创建线程
- 使用线程池例如用Executor框架
- 目前使用手段最常用:使用匿名类创建 Thread 子类对象 || 使用匿名类创建 Runnable 子类对象 || 用 lambda 表达式创建 Runnable 子类对象
线程在执行过程中几种状态:
- 就绪(Runnable):线程准备运行,不一定立马就能开始执行。
- 运行中(Running):进程正在执行线程的代码。
- 等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。
- 睡眠中(Sleeping):线程被强制睡眠。
- I/O阻塞(Blocked on I/O):等待I/O操作完成。
- 同步阻塞(Blocked on Synchronization):等待获取锁。
- 死亡(Dead):线程完成了执行。
同步方法和同步代码块的区别是什么?
在Java语言中,每一个对象有一把锁。线程可以使用synchronized关键字来获取对象上的锁。synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。
Thread 类中的start() 和 run() 方法有什么区别?
start()方法被用来启动新创建的线程,而且start()内部 调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程。
Java中Runnable和Callable有什么不同?
重点:Callable可以返回装载有计算结果的Future对象。
Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。
Java中的volatile 变量是什么?
volatile是一个特殊的修饰符,只有成员变量才能使用它。保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
** Java中堆和栈有什么不同?**
因为栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈 调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己 的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。
Java中notify 和 notifyAll有什么区别?
notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。
Java多线程中调用wait() 和 sleep()方法有什么不同?
Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁.
什么是ThreadLocal变量?
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
什么是FutureTask?
在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完 成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包 装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。
Java中interrupted 和 isInterrupted方法的区别?
interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。
什么是线程池? 为什么要使用它?
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时 候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短 的任务的程序的可扩展线程池)。
Java中synchronized 和 ReentrantLock 有什么不同?
Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。实现加锁次数和释放次数不一样,第一个线程没有什么影响,但是由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
ReentrantLock扩展锁之外的方法或者块边界,尝试获取锁 时不能中途取消等
lock.unlock();/正常情况,如锁几次就要解锁几次.
什么叫可重入锁: 同步代码块或者同步方法中,加入关键字synchronized,之后外层获取到锁了,中层,内层不需要释放锁就能直接获取
hread类中的yield方法有什么作用?
Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。
Java中Semaphore是什么?
Java中的Semaphore是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前 会阻塞每一个 acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采 取相应的行动。信号量常常用于多线程的代码中,比如数据库连接池。
Java线程池中submit() 和 execute()方法有什么区别?
两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线 程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。
Java中的ReadWriteLock是什么?
一般而言,读写锁是用来提升并发程序性能的锁分离技术的成果。Java中的ReadWriteLock是Java 5 中新增的一个接口,一个ReadWriteLock维护一对关联的锁,一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程 持有。写锁是独占的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则,它最多支持65535个写锁和65535个读 锁。
多线程中的忙循环是什么?
忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可 能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。
volatile 变量和 atomic 变量有什么不同?
Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操作就不是原子性的。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性 的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。
如果同步块内的线程抛出异常会发生什么?
无论你的同步块是正常还是异常退出的,里面的线程都会释放锁,所以对比锁接口我更喜欢同步块,因为它不用我花费精力去释放锁,该功能可以在finally block里释放锁实现。
单例模式
《 单例模式就是确保只有一个实例,而且自行实例化并向整个系统传递这个实例,这个类就称作为单例类,单例模式最重要的一个特点就是构造方法私有化。创建单例模式分为懒汉式和饿汉式。》
- 懒汉式(线程不安全的)
所谓懒汉式单例模式就是在调用的时候才去创建这个实例。这种写法的懒加载很明显,但是缺点就是不能再多线程访问下正常工作。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 第二种:懒汉式(线程安全的)
线程安全的方式创建单例就是在对外的创建实例方法上加上synchronized。 这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 第三种:饿汉式(是线程安全的)
这种方式基于 classloder 机制避免了多线程的同步问题,不过, instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
- 第四种:静态内部类的方式创建单例模式(static inner class)
这种方式同样利用了 classloder 的机制来保证初始化 instance 时只有一个线程.第三种方式是只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了, instance 不一定被初始化。
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {// 静态内部类
private static Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
- 第五种:双重校验锁(dubble check instance)
这种事用双重判断来创建一个单例的方法,那么我们为什么要使用两个if判断这个对象当前是不是空的呢 ?因为当有多个线程同时要创建对象的时候,多个线程有可能都停止在第一个if判断的地方,等待锁的释放,然后多个线程就都创建了对象,这样就不是单例模式了,所以我们要用两个if来进行这个对象是否存在的判断。 可能好多人在想第五种方式创建单例模式的时候如果把synchronized放在第一个if不就好了吗?如果我们把synchronized放在第一个id上面,那么我们第五种方式创建单例就和线程安全的懒汉式创建单例是同一种了!
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance(){
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
/**
* 为什么这里会使用双重判定呢?
*/
singleton = new Singleton();
}
}
}
return singleton;
}
}
单例模式的双检锁是什么?
解决了首次创建对象的唯一性问题。双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。
双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。
遵循的多线程最佳实践规则
- 给你的线程起个有意义的名字。
- 避免锁定和缩小同步的范围
- 多用同步类少用wait 和 notify
- 多用并发集合少用同步集合
强制启动一个线程?
这个问题就像是如何强制进行Java垃圾回收,目前还没有觉得方法,虽然你可以使用System.gc()来进行垃圾回收,但是不保证能成功。在Java里面没有办法强制启动一个线程,它是被线程调度器控制着且Java没有公布相关的API。
Java中的fork join框架是什么?
fork / join框架在Java 7中提供。它提供了一些工具,通过尝试使用所有可用的处理器内核来帮助加速并行处理 - 这是通过分而治之的方法实现的。这意味着框架首先“forks”,递归地将任务分解为较小的独立子任务,直到它们足够简单以便异步执行。之后,“join”部分开始,其中所有子任务的结果以递归方式连接到单个结果中,或者在返回void的任务的情况下,程序只是等待直到执行完每个子任务。fork / join框架使用一个名为ForkJoinPool的线程池,它管理ForkJoinWorkerThread类型的工作线程。
fork join框架是JDK7中出现的一款高效的工具,Java开发人员可以通过它充分利用现代服务器上的多处理器。它是专门为了那些可以递归划分成许多子模块 设计的,目的是将所有可用的处理能力用来提升程序的性能。fork join框架一个巨大的优势是它使用了工作窃取算法,可以完成更多任务的工作线程可以从其它线程中窃取任务来执行。
扩展1:
JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
扩展2:
在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。
扩展3:
Java中ConcurrentHashMap的并发度是什么?ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是 ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
Java入门到入坟
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南