什么是多线程中的上下文切换?
上下文切换是指在一个 CPU 上,由于多个线程共享 CPU 时间片,当一个线程的时间片用完后,切换到另一个线程继续执行。
这个时候需要保存当前线程的状态信息,包括程序计数器、寄存器,以便下次继续执行该线程时能够恢复到正确的执行状态。同时还需要将切换到的线程的状态信息恢复,以便于该线程能够正确运行。
在多线程中,上下文切换的开销比直接用单线程大,因为在多线程中,需要保存和恢复更多的上下文信息。过多的上下文切换会降低系统的运行效率,因此需要尽可能减少上下文切换的次数。
怎么减少上下文切换?
频繁的上下文切换会导致CPU时间的浪费,因此在多线程编程时需要尽可能地避免它。
-
减少线程数:可以通过合理的线程池管理来减少线程的创建和销毁,线程数不是越多越好,合理的线程数可以避免线程过多导致上下文切换。
-
使用无锁并发编程:无锁并发编程可以避免线程因等待锁而进入阻塞状态,可以减少上下文切换的发生。尽量缩小同步代码块或同步方法的范围,从而减少线程的等待时间
-
采用CAS算法:CAS算法可以避免线程的阻塞和唤醒操作,从而减少上下文切换
谈谈你对线程安全的理解
线程安全是指某个方法在并发环境中被调用,能正确的处理多个线程之间的共享变量不会出现异常情况,使程序按照程序设计的情况执行
什么是并发,什么是并行?
- 并发:是指一个时间段中有几个程序都处于运行,这几个程序都是在同一个处理机上运行。在我们操作系统现在都是多用户多任务分时操作系统,这可以使这些操作系统的用户可以同时干很多事情。
- 并行:当电脑有多个CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行。
线程有几种状态,状态之间的流转是什么样的?
Java线程中状态有6种。
- 初始:新创建了一个线程对象,还没调用运行方法·。
- 运行:Java线程中将就绪和运行中两种状态笼统的称为“运行”
-
- 就绪:线程对象被创建后,调用了启动,但是还在等待cpu调用;
- 运行中:开始运行程序代码
- 阻塞:线程进入阻塞
- 等待:线程需要等待其他线程做出一些特定动作,进行唤醒
- 超时等待:它可以在指定的时间后继续执行。
- 终止:表示该线程已经执行完毕
什么是守护线程,和普通线程有什么区别
在Java中有两类线程分别是用户线程、守护线程。
- 用户线程:一般用于执行用户级任务
- 守护线程:守护线程是后台线程,一般用来执行后台任务,像GC垃圾回收器就是守护线程
创建线程的方式有几种?
- 继承Thread(死赖得)类创建线程
- 实现Runnable烂了波)接口创建线程
- 通过Callable和FutureTask创建线程
- 通过线程池创建线程
Runnable和Callable区别
Runnable接口和Callable接口都可以用来创建新线程,实现Runnable的时候,需要实现run方法;实现Callable接口的话,需要实现call方法。
Runnable的run方法无返回值,Callable的call方法有返回值,类型为Object
什么是线程池,如何实现的?
线程池是一种池化技术的实现,它会提前保存大量的资源,以备不时之需。就是提前创建好一批线程,然后保存在线程池中,当有任务需要执行的时候,从线程池中选一个线程来执行任务。
线程池的实现原理
看想不想记录吧!
线程数设定成多少更合适
线程数的设定需要根据应用程序的需求和运行环境来决定,没有一个固定的通用值。
主要参考:CPU核数、应用类型、JVM、系统资源。
由于因素过多,没有一个通用的公式,但是有一些公式可以参考比如:
- 如果是CPU密集型应用,则线程池大小设置CPU核数+1
- 如果是IO密集型应用,则线程池大小设置为CPU核数*2+1
在程序开发中,不建议套用公式,还是应该根据实际情况来设置。
什么是CPU密集型和IO密集型
- CPU密集型:执行过程中主要依赖于CPU处理能力的任务
- IO密集型:指在执行过程中主要依赖于IO操作(如磁盘读写、网络通信)的任务
ThreadLocal的应用场景有哪些
ThreadLocal 提供了一种线程级别的数据存储机制,每个线程都拥有自己独立的 ThreadLocal 副本,这意味着每个线程都可以独立地、安全地操作这些变量,而不会影响其他线程。
主要就解决
- 解决并发的问题
- 在线程中传递数据,在同一个线程执行过程中,ThreadLocal的数据一直在,所以我们可以在前面把数据放到ThreadLocal中,然后再后面的时候再取出来用,就可以避免要把这些数据一直通过参数传递
线程同步的方式有哪些
线程同步是指多个线程之间按照顺序访问同一个共享资源,避免因为并发冲突导致的问题。
synchronized:Java中最基本的线程同步机制
ReentrantLock: 支持公平锁、可中断锁、多个条件变量等功能。
Semaphore: 允许多个线程同时访问共享资源,但是限制访问的线程数量。可以用于控制并发访问的线程数量,避免系统资源被过度占用。
什么是死锁,怎么解决死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源造成的一种阻塞的现象,若没有第三方干扰,它们都无法进行下去。如果发生了死锁,会永远互相等待对方先释放死锁。
就比如:12306买票上车,没买到票的想先上车在补票,12306必须要你买票在上车。
解除和避免锁
-
破坏循环等待:保证多个线程的执行顺序相同即可避免循环等待。
-
比如先执行事务1执行A-B-C,事务2改成A-D-C,如果是执行C-A-D会重新死锁
并发编程中的原子性和数据库ACID的原子性一样吗?
在数据库中,事务的ACID中原子性指的是"要么都执行要么都回滚"。
在并发编程中,原子性指的是"操作不可拆分、不被中断"。所以在并发编程中,我们要保证原子性指的就是一段代码需要不可拆分,不被中断。
所以它们是不一样的
synchronized是怎么实现的
synchronized(深哥莱斯)是Java 中的一个关键字,主要用来加锁,可以用来修饰方法和代码块,可以根据锁的对象不同,可以用来定义同步方法和同步代码块。
同步方法和同步代码块实现是不同的:
- 同步方法:常量池中会有一个标记,当某个线程要访问某个方法的时候,会检查是否有这个标记,如果有设置就获取得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被堵塞住。
- 同步代码:它有两个指令,一个加锁一个释放锁,每个对象维护着一个记录着被锁次数的计数器,当计数器为 0 的时候,锁被释放。
synchronized(深哥莱斯)加在方法上面就是同步方法,加在代码块中就是同步代码块
synchronized如何保证有序性
synchronized修饰的代码,同一时间只能被同一线程访问。那么也就是单线程执行的。所以,可以保证其有序性。
什么是AQS
AQS是很多锁的父接口,我们可以基于AQS实现自定义同步器。
什么是CAS?存在什么问题?
CAS是一项乐观锁技,顾名思义就是先比较再替换
什么是乐观锁悲观锁?
- 乐观锁:乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测。CAS就是一种乐观锁
- 悲观锁:悲观锁与乐观锁相反,对数据的修改抱有悲观态度的并发控制方式,一般在认为数据被并发修改的概率比较大的时候,需要在修改之前先加锁的时候使用。synchronized就是一种悲观锁。
CAS和synchronized那个是悲观锁那个是乐观锁
CAS是乐观锁的实现,synchronized就是悲观锁
当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败。
公平锁和非公平锁的区别
- 非公平锁:多个线程不按申请锁的顺序去获取锁,获取不到,再进入队列等待,如果能获取到,就直接拿到锁。
- 公平锁:多个线程按照申请锁的顺序去获得锁,所有线程都在队列里排队,这样就保证了队列中的第一个先得到锁。
父子线程之间怎么共享/传递数据
当我们在同一个线程中,想要共享变量的话,是可以直接使用ThreadLocal的,但是如果在父子线程之间,可以在子线程中创建一个成员变量,这样在主线程创建子线程的时候,可以给成员变量赋值,这样实现数据共享。
有三个线程T1,T2,T3如何保证顺序执行
想要让多个线程依次执行,并且按照顺序,要么线程之间可以通信、要么采用排队。
- 通信:可以使用join方法实现,还有其他方式
- 排队:可以用线程池,或者CompletableFuture的方式来实现。
三个线程分别顺序打印0-100
可以使用Synchronized加LOCK来实现,操作一次锁一次然后打印后在释放锁。
当一个线程尝试获取锁或者同步器时,如果获取失败,AQS会将该线程封装成一个Node并添加到等待队列中进行堵塞
当一个线程释放锁或者同步器时,AQS会
AQS是如何实现线程的等待和唤醒的
AQS是Java中实现锁和同步器的基础类,AQS中线程等待和唤醒主要依赖park和unpark实现的,将等待队列中的第一个线程唤醒,并让其重新尝试获取锁或者同步器。
如何保证多线程下 i++ 结果正确
想要保证多线程情况下,i++的正确性,需要考虑可见性、原子性及有序性。
可以使用synchronized、或者加锁的方式,用redis也inr也是可以的
Thread.sleep(0)的作用是什么?
这个方法是让线程休眠,但是如果设置0也就是休眠没有生效,但是可以让线程暂时释放CPU资源。
有哪些实现线程安全的方案?
最简单的办法就是,直接使用单线程,然后使用互斥锁、还有就是读写分离。
-
单线程:比较简单就不解释了
-
互斥锁:就是排队,一个一个来
-
读写分离:写时复制,先拷贝一份原来的数据,然后在新的数据里面进行操作,写完后在指向回去。
为什么不建议通过Executors构建线程池
在阿里巴巴开发手册里面是建议使用ThreadPoolExecutor的方式,说这种方式可以让开发者明确线程池的运行规则,避免资源耗尽。
线程池的拒绝策略有哪些?
默认的拒绝策略:当线程池无法接受新任务时,会抛出异常
还有个拒绝策略在线程满了会抛弃任务
然后还有一个也会丢弃任务,但是它会丢弃最早的任务
另一个策略将任务回退给调用线程,而不会泡池异常
Java是如何判断一个线程是否存活的
可以通过Thread下的isAlive()方法
什么是可重入锁,怎么实现可重入锁
可重入锁是一种多线程同步机制,允许同一线程多次获取同一个锁而不会导致死锁。
意味着一个线程可以在持有锁的情况下再次请求并获得相同的锁,而不会被自己阻塞
synchronized就是可重入锁。
如何实现主线程捕获子线程异常
可以使用Future,可以拿到线程执行结果
为什么不能在try-catch中捕获子线程的异常
因为它们的执行是并发的,因此主线程无法捕获子线程的异常。子线程的异常通常由子线程自己处理或通过适当的异常处理机制处理。
如何让Java的线程池顺序执行任务
Java中的线程池本身并不提供内置的方式来保证任务的顺序执行的,因为线程池的设计目的是为了提高并发性能和效率,如果顺序执行的话,那就和单线程没区别了。
但是我们人为干预,可以使用一个什么线程池,线程池里面只有一个线程,它可以保证任务被顺序执行。
还有就是执行让其他线程等待,但这个任务执行完成在将其唤醒。
ThreadLocal的应用场景有哪些?
ThreadLocal 提供了一种线程级别的数据存储机制。每个线程都拥有自己独立的 ThreadLocal 副本,这意味着每个线程都可以独立地、安全地操作这些变量,而不会影响其他线程。
- 用户身份信息存储、日志上下文存储
ThreadLocal为什么会导致内存泄漏?如何解决的?
ThreadLocal内存泄漏的部分其实就是他在堆上存储的ThreadLocalMap中的K-V部分,
Thread对象如果一直在被使用,比如在线程池中被重复使用,那么从这条引用链就一直在,那么就会导致ThreadLocalMap无法被回收。
解决的话
ThreadLocalMap底层使用数组来保存元素,只需要用完之后手动remove就好了。
有了CAS为啥还需要volatile?
CAS底层其实是在总线上面加了锁,CAS只能保证原子性,即一个修改命令,以原子性的操作完成,中间不会被中断。但是在并发场景下,我们要解决的问题还包括有序性和可见性。
volatile除了可以保证数据的可见性之外,还有一个强大的功能,那就是他可以禁止指令重排优化,这就保证了代码的程序会严格按照代码的先后顺序执行。
什么是AQS的独占模式和共享模式
- 独占模式:意味着一次只有一个线程可以获取同步状态,这种模式通常用于实现互斥锁。
- 共享模式:允许多个线程同时获取同步状态。这种模式通常用于实现如信号量和读写锁。
sychronized是非公平锁吗,那么是如何体现的?
在多线程环境中,公平锁保证了等待获取锁的线程按照请求锁的顺序来获取锁。也就是说,先请求锁的线程会先获得锁。
非公平锁则不保证等待获取锁的线程的执行顺序。这意味着即使某个线程最早请求锁,也可能会在其他后来请求锁的线程之后获得锁。非公平锁可能会导致“饥饿”问题,但通常具有更高的吞吐量。
synchronized 并没有明确规定锁的公平性,但在实际情况来看,它是一种非公平锁。