Java并发篇
java线程和操作系统的线程一样吗?
Java线程是基于操作系统线程实现的。Java虚拟机(JVM)将Java线程映射到操作系统的原生线程上,但Java提供了更高层次的线程抽象,简化了线程管理。
使用多线程要注意哪些问题?
-
线程安全:确保多个线程访问共享资源时数据的一致性。
-
资源竞争:避免多个线程竞争同一资源导致的性能问题。
-
死锁:多个线程相互等待对方释放资源,导致程序无法继续执行。
-
性能开销:线程创建和销毁需要消耗系统资源,过多线程可能导致性能下降。
保证数据一致性有哪些方案?
-
锁:使用互斥锁(如
synchronized
和ReentrantLock
)来保护共享资源。 -
原子变量:使用
AtomicInteger
等原子类来保证操作的原子性。 -
不可变对象:使用不可变对象来避免修改,从而保证数据一致性。
线程的创建方式有哪些?
-
继承Thread类:创建一个类继承自
Thread
类,并重写run
方法。 -
实现Runnable接口:创建一个类实现
Runnable
接口,并实现run
方法。 -
实现Callable接口:创建一个类实现
Callable
接口,并实现call
方法。 -
使用ExecutorService:使用线程池来创建和管理线程。
怎么启动线程?
调用Thread
对象的start()
方法来启动线程。
如何停止一个线程的运行?
通过调用线程的interrupt()
方法来请求线程停止。线程应该定期检查中断状态,并在适当的时候停止执行。
调用interrupt是如何让线程抛出异常的?
调用interrupt()
方法会设置线程的中断状态。如果线程在执行某些阻塞操作(如sleep()
、wait()
等)时被中断,会抛出InterruptedException
。
Java线程的状态有哪些?
-
新建(New):线程被创建但尚未启动。
-
就绪(Runnable):线程已经准备好运行,等待JVM调度。
-
运行(Running):线程正在JVM中执行。
-
阻塞(Blocked):线程等待获取一个排他锁,以便进入同步代码块或同步方法。
-
等待(Waiting):线程等待另一个线程执行特定的操作。
-
超时等待(Timed Waiting):线程等待另一个线程执行特定的操作,但有等待时间限制。
-
终止(Terminated):线程已经执行完毕或被强制停止。
blocked和waiting有啥区别?
-
Blocked:线程等待获取一个排他锁,以便进入同步代码块或同步方法。
-
Waiting:线程等待另一个线程执行特定的操作(如调用
notify()
或notifyAll()
)。
wait状态下的线程如何进行恢复到running状态?
通过其他线程调用notify()
或notifyAll()
方法,将等待状态的线程唤醒。
notify和notifyAll的区别?
-
notify():唤醒在此对象监视器上等待的一个线程。
-
notifyAll():唤醒在此对象监视器上等待的所有线程。
notify选择哪个线程?
JVM会选择一个等待状态的线程进行唤醒,选择过程是非确定性的。
并发安全
并发安全是指在多线程环境下,程序的行为仍然正确,数据的一致性和完整性得到保证。
juc包下你常用的类?
-
ReentrantLock:可重入互斥锁。
-
Semaphore:信号量,用于控制同时访问特定资源的线程数量。
-
CountDownLatch:倒计数锁存器,用于等待其他线程完成操作。
-
CyclicBarrier:循环屏障,用于等待一组线程在屏障处汇合。
怎么保证多线程安全?
-
同步机制:使用
synchronized
关键字或显式锁(如ReentrantLock
)来保护共享资源。 -
原子变量:使用
AtomicInteger
等原子类来保证操作的原子性。 -
不可变对象:使用不可变对象来避免修改,从而保证数据一致性。
Java中有哪些常用的锁,在什么场景下使用?
-
synchronized:适用于简单的同步需求,使用方便但不够灵活。
-
ReentrantLock:提供更灵活的锁操作,如可中断、可超时、可公平。
-
ReadWriteLock:读写锁,允许多个线程同时读取,但写入时需要独占。
怎么在实践中用锁的?
-
synchronized:通过在方法或代码块前加上
synchronized
关键字来实现同步。 -
ReentrantLock:通过创建
ReentrantLock
对象,并在需要同步的代码块中调用lock()
和unlock()
方法来实现同步。
Java并发工具你知道哪些?
-
ExecutorService:线程池,用于管理线程的生命周期。
-
CountDownLatch:倒计数锁存器,用于等待其他线程完成操作。
-
CyclicBarrier:循环屏障,用于等待一组线程在屏障处汇合。
-
Semaphore:信号量,用于控制同时访问特定资源的线程数量。
synchronized和reentrantlock及其应用场景?
-
synchronized:适用于简单的同步需求,使用方便但不够灵活。
-
ReentrantLock:提供更灵活的锁操作,如可中断、可超时、可公平,适用于需要更复杂同步控制的场景。
synchronized和reentrantlock区别?
-
synchronized:不可中断,不可超时,不可选择公平性。
-
ReentrantLock:可中断,可超时,可选择公平性。
怎么理解可重入锁?
可重入锁是指同一个线程可以多次获取同一把锁,而不会导致死锁。
synchronized支持重入吗?如何实现的?
支持。synchronized
通过一个计数器记录同一线程获取锁的次数,每次获取锁时计数器加一,释放锁时计数器减一,当计数器为零时锁被释放。
synchronized锁升级的过程讲一下
-
偏向锁:线程第一次获取锁时,锁会偏向该线程。
-
轻量级锁:当其他线程尝试获取锁时,会尝试通过自旋来获取锁。
-
重量级锁:当自旋一定次数后仍未获取到锁,会升级为重量级锁,使用操作系统的互斥锁来保护共享资源。
JVM对Synchronized的优化?
-
锁粗化:将多个连续的同步块合并成一个同步块。
-
锁消除:在编译时去掉不可能发生数据竞争的锁。
-
锁升级:根据锁的使用情况,自动将锁从偏向锁升级为轻量级锁,再升级为重量级锁。
介绍一下TAQS
TransferQueue
是一个支持传输操作的队列。它允许生产者在无法立即将元素传输给消费者时等待,直到消费者准备好接收元素。
ThreadLocal作用,原理,具体里面存的key value是啥,会有什么问题,如何解决?
-
作用:
ThreadLocal
用于为每个线程提供独立的变量副本,避免线程间的数据共享。 -
原理:
ThreadLocal
为每个线程维护一个独立的变量副本,通过线程ID来索引。 -
存储内容:
ThreadLocal
中存储的是键值对,键是ThreadLocal
对象,值是线程的局部变量。 -
问题:可能导致内存泄漏,因为线程结束时,
ThreadLocal
中的数据不会被自动清理。 -
解决方法:在不再需要
ThreadLocal
变量时,调用remove()
方法来清理数据。
Java中想实现一个乐观锁,都有哪些方式?
-
CAS操作:使用
Atomic
类提供的CAS操作来实现乐观锁。 -
版本号:在数据中增加版本号,每次更新时检查版本号是否匹配。
CAS有什么缺点?
-
ABA问题:CAS操作可能遇到ABA问题,即值从A变为B,再变回A,CAS操作无法检测到这种变化。
-
循环时间长:CAS操作可能需要长时间自旋,导致CPU占用高。
为什么不能所有的锁都用CAS?
CAS适用于单个变量的原子操作,不适合复杂的同步场景,如需要锁重入、可中断、可超时等特性的场景。
volatile关键字有什么作用?
volatile
关键字用于保证变量的可见性和有序性,确保一个线程对变量的修改对其他线程立即可见,并防止指令重排序。
指令重排序的原理是什么?
编译器和处理器为了优化性能,可能会改变指令的执行顺序,这可能导致多线程环境下的数据不一致问题。
volatile可以保证线程安全吗?
volatile
只能保证变量的可见性和有序性,不能保证原子性,因此不能保证线程安全。
volatile和synchronized比较?
-
synchronized:块级锁定,提供原子性、可见性和有序性。
-
volatile:变量级,只提供可见性和有序性,不保证原子性。
什么是公平锁和非公平锁?
-
公平锁:按照请求锁的顺序来分配锁,先请求的线程先获取锁。
-
非公平锁:不保证按照请求锁的顺序来分配锁,可能导致某些线程长时间等待。
非公平锁吞吐量为什么比公平锁大?
非公平锁允许某些线程插队获取锁,从而减少了线程等待时间,提高了并发度和吞吐量。
Synchronized是公平锁吗?
不是,synchronized
是非公平锁。
ReentrantLock是怎么实现公平锁的?
通过在ReentrantLock
的构造函数中设置fairness
参数为true
来实现公平锁。
什么情况会产生死锁问题?如何解决?
死锁产生的四个必要条件是:互斥、占有、不可剥夺、循环等待。解决方法包括:
-
避免一个线程同时占有多个资源。
-
资源分配顺序:按照一定的顺序分配资源。
-
超时机制:设置获取资源的超时时间。
-
死锁检测:使用工具检测并解决死锁。
线程池
介绍下线程池的工作原理
线程池通过复用线程来减少线程创建和销毁的开销。它维护一个线程池,根据任务需求来复用已有线程或创建新线程。
线程池的参数有哪些?
-
核心线程数:线程池中始终保持的线程数量。
-
最大线程数:线程池中允许的最大线程数量。
-
工作队列:用于存放等待执行的任务。
-
线程存活时间:非核心线程空闲时在终止前等待新任务的最长时间。
-
时间单位:线程存活时间的时间单位。
-
线程工厂:用于创建新线程的工厂。
-
拒绝策略:当任务太多,无法被线程池及时处理时,采取的策略。
线程池工作队列满了有哪些拒绝策略?
-
抛出异常:直接抛出
RejectedExecutionException
。 -
丢弃任务:直接丢弃任务,不进行任何处理。
-
调用者执行:将任务返回给提交任务的线程执行。
-
丢弃最旧任务:丢弃最早的未处理任务,然后尝试重新提交当前任务。
有线程池参数设置的经验吗?
根据任务特性和系统资源合理设置线程池参数。例如,对于CPU密集型任务,核心线程数可以设置为CPU核心数的1到1.5倍;对于IO密集型任务,核心线程数可以设置为CPU核心数的2到3倍。
核心线程数设置为0可不可以?
可以,但可能会导致所有任务都在工作队列中等待,无法及时处理。
线程池种类有哪些?
-
固定大小线程池:线程数量固定的线程池。
-
单线程线程池:只有一个线程的线程池。
-
缓存线程池:根据需要创建新线程,对于空闲超过一定时间的线程进行回收。
-
定时及周期性任务线程池:用于延迟执行或定期执行任务的线程池。
线程池一般是怎么用的?
通过创建线程池对象,然后使用execute()
或submit()
方法提交任务给线程池执行。线程池会根据任务需求来复用已有线程或创建新线程。
线程池中shutdown()、shutdownNow()这两个方法有什么作用?
-
shutdown():平滑关闭线程池,不再接受新任务,但会完成所有已提交的任务。
-
shutdownNow():立即关闭线程池,不再接受新任务,并尝试中断所有正在执行的任务。
提交给线程池中的任务可以被撤回吗?
可以,通过Future.cancel()
方法来撤回任务。
场景
多线程打印奇偶数,怎么控制打印的顺序
可以使用两个线程分别打印奇数和偶数,并通过同步机制(如synchronized
或ReentrantLock
)来控制打印顺序。
单例模型既然已经用了synchronized,为什么还要在加volatile?
volatile
确保实例的可见性和有序性,防止指令重排序。在单例模型中,volatile
可以确保在多线程环境下,实例的正确性和及时可见性。