2021年JAVA多线程并发编程⾯试题(持续更新)
这⾥写⽬录标题
并发编程基础
blocked 和 waiting 的区别
BLOCKED 和 WAITING 都是⾮活动线程的状态. WAITING 线程是已经分配到了CPU时间,但是需要等待事件发⽣所以主动释放了CPU,直到某些事件完成后调⽤了notify()唤醒, 也就是WAITTING线程是⾃⼰现在不想要CPU时间,但是BLOCKED线程是想要的,但是BLOCKED线程没有获得锁,所以轮不到BLOCKED线程。
buu
线程的 run()和 start()有什么区别?
每个线程都是通过某个特定Thread对象所对应的⽅法run()来完成其操作的,run()⽅法称为线程体。通过调⽤Thread类的start()⽅法来启动⼀个线程。
start() ⽅法⽤于启动线程,run() ⽅法⽤于执⾏线程的运⾏时代码。run() 可以重复调⽤,⽽ start() 只能调⽤⼀次。
start()⽅法来启动⼀个线程,真正实现了多线程运⾏。调⽤start()⽅法⽆需等待run⽅法体代码执⾏完毕,可以直接继续执⾏其他的代码;此时线程是处于就绪状态,并没有运⾏。 然后通过此Thread类调⽤⽅法run()来完成其运⾏状态, run()⽅法运⾏结束, 此线程终⽌。然后CPU再调度其它线程。
run()⽅法是在本线程⾥的,只是线程⾥的⼀个函数,⽽不是多线程的。 如果直接调⽤run(),其实就相当于是调⽤了⼀个普通函数⽽已,直接待⽤run()⽅法必须等待run()⽅法执⾏完毕才能执⾏下⾯的代码,所以执⾏路径还是只有⼀条,根本就没有线程的特征,所以在多线程执⾏时要使⽤start()⽅法⽽不是run()⽅法。
为什么我们调⽤ start() ⽅法时会执⾏ run() ⽅法,为什么我们不能直接调⽤ run() ⽅法?
这是另⼀个⾮常经典的 java 多线程⾯试问题,⽽且在⾯试中会经常被问到。很简单,但是很多⼈都会答不上来!
new ⼀个 Thread,线程进⼊了新建状态。调⽤ start() ⽅法,会启动⼀个线程并使线程进⼊了就绪状态,当分配到时间⽚后就可以开始运⾏了。 start() 会执⾏线程的相应准备⼯作,然后⾃动执⾏ run() ⽅法的内容,这是真正的多线程⼯作。
⽽直接执⾏ run() ⽅法,会把 run ⽅法当成⼀个 main 线程下的普通⽅法去执⾏,并不会在某个线程中执⾏它,所以这并不是多线程⼯作。
总结: 调⽤ start ⽅法⽅可启动线程并使线程进⼊就绪状态,⽽ run ⽅法只是 thread 的⼀个普通⽅法调⽤,还是在主线程⾥执⾏。
说说线程的⽣命周期及五种基本状态?
1. 新建(new):新创建了⼀个线程对象。
2. 可运⾏(runnable):线程对象创建后,当调⽤线程对象的 start()⽅法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使⽤
权。
3. 运⾏(running):可运⾏状态(runnable)的线程获得了cpu时间⽚(timeslice),执⾏程序代码。注:就绪状态是进⼊到运⾏状态的唯
⼀⼊⼝,也就是说,线程要想进⼊运⾏状态执⾏,⾸先必须处于就绪状态中;
4. 阻塞(block):处于运⾏状态中的线程由于某种原因,暂时放弃对 CPU的使⽤权,停⽌执⾏,此时进⼊阻塞状态,直到其进⼊到就绪
状态,才 有机会再次被 CPU 调⽤以进⼊到运⾏状态。
阻塞的情况分三种:
(⼀). 等待阻塞:运⾏状态中的线程执⾏ wait()⽅法,JVM会把该线程放⼊等待队列(waitting queue)中,使本线程进⼊到等待阻塞状态;
英语4级报名时间
(⼆). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占⽤),,则JVM会把该线程放⼊锁池(lock pool)中,线程会进⼊同步阻塞状态;
(三). 其他阻塞: 通过调⽤线程的 sleep()或 join()或发出了 I/O 请求时,线程会进⼊到阻塞状态。当 sleep()状态超时、join()等待线程
终⽌或者超时、或者 I/O 处理完毕时,线程重新转⼊就绪状态。
5. 死亡(dead):线程run()、main()⽅法执⾏结束,或者因异常退出了run()⽅法,则该线程结束⽣命周期。死亡的线程不可再次复⽣。
Java 中⽤到的线程调度算法是什么?
psb
女生祛痘有两种调度模型:分时调度模型和抢占式调度模型。
线程同步以及线程调度相关的⽅法。
(1) wait():使⼀个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
(2)sleep():使⼀个正在运⾏的线程处于睡眠状态,是⼀个静态⽅法,调⽤此⽅法要处理 InterruptedException 异常;
(3)notify():唤醒⼀个处于等待状态的线程,当然在调⽤此⽅法的时候,并不能确切的唤醒某⼀个等待状态的线程,⽽是由 JVM 确定唤醒哪个线程,⽽且与优先级⽆关;
(4)notityAll():唤醒所有处于等待状态的线程,该⽅法并不是将对象的锁给所有线程,⽽是让它们竞争,只有获得锁的线程才能进⼊就绪状态;
sleep() 和 wait() 有什么区别?
两者都可以暂停线程的执⾏
类的不同:sleep() 是 Thread线程类的静态⽅法,wait() 是 Object类的⽅法。
是否释放锁:sleep() 不释放锁;wait() 释放锁。
⽤途不同:Wait 通常被⽤于线程间交互/通信,sleep 通常被⽤于暂停执⾏。
⽤法不同:wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或者 notifyAll() ⽅法。sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤wait(long timeout)超时后线程会⾃动苏醒。
线程的 sleep()⽅法和 yield()⽅法有什么区别?
(1) sleep()⽅法给其他线程运⾏机会时不考虑线程的优先级,因此会给低优先级的线程以运⾏的机会;yield()⽅法只会给相同优先级或更⾼优先级的线程以运⾏的机会;
(2) 线程执⾏ sleep()⽅法后转⼊阻塞(blocked)状态,⽽执⾏ yield()⽅法后转⼊就绪(ready)状态;
(3)sleep()⽅法声明抛出 InterruptedException,⽽ yield()⽅法没有声明任何异常;
(4)sleep()⽅法⽐ yield()⽅法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议使⽤yield()⽅法来控制并发线程的执⾏。同步⽅法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步⽅法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停⽌执⾏并需要等待获得这个对象上的锁。
同步块更要符合开放调⽤的原则,只在需要锁住的代码块锁住相应的对象,这样从侧⾯来说也可以避免死锁。
请知道⼀条原则:同步的范围越⼩越好。
如果你提交任务时,线程池队列已满,这时会发⽣什么
这⾥区分⼀下:
(1)如果使⽤的是⽆界队列 LinkedBlockingQueue,也就是⽆界队列的话,没关系,继续添加任务到阻塞队列中等待执⾏,因为LinkedBlockingQueue 可以近乎认为是⼀个⽆穷⼤的队列,可以⽆限存放任务
(2)如果使⽤的是有界队列⽐如 ArrayBlockingQueue,任务⾸先会被添加到ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使⽤拒绝策略RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy
什么叫线程安全?rvlet、controller 是线程安全吗?
线程安全是编程中的术语,指某个⽅法在多线程环境中被调⽤时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。Servlet 不是线程安全的,rvlet 是单实例多线程的,当多个线程同时访问同⼀个⽅法,是不能保证共享变量的线程安全性的。
Struts2 的 action 是多实例多线程的,是线程安全的,每个请求过来都会 new ⼀个新的 action 分配给这个请求,请求完成后销毁。SpringMVC 的 Controller 是线程安全的吗?不是的,和 Servlet 类似的处理流程。
walk offStruts2 好处是不⽤考虑线程安全问题;Servlet 和 SpringMVC 需要考虑线程安全问题,但是性能可以提升不⽤处理太多的 gc,可以使⽤ ThreadLocal 来处理多线程的问题。
在 Java 程序中怎么保证多线程的运⾏安全?
⽅法⼀:使⽤安全类,⽐如 urrent 下的类,使⽤原⼦类AtomicInteger
⽅法⼆:使⽤⾃动锁 synchronized。
⽅法三:使⽤⼿动锁 Lock。
⼿动锁的代码:
Lock lock =new ReentrantLock();
lock.lock();
try{
System. out.println("获得锁");
}catch(Exception e){
cio是什么意思// TODO: handle exception
come up with}finally{
System. out.println("释放锁");
中译日
lock.unlock();
}
如何保证线程同步
1. synchronized 修饰⽅法
2. synchronized 修饰代码块
3. ReentrantLock
4. 使⽤ volatile
Synchronized:
Synchronized 原理
java虚拟机⾥⾯的同步是基于进⼊和退出monitor对象实现的,⽆论是显式同步(同步代码块)还是隐式同步都是如此,当同步⽅法的时候并不是由monitorenter和monitorexit指令来实现同步的,⽽是由⽅法调⽤指令读取运⾏时常量池中的表结构的ACC_SYNCHRONIZED标志来隐式实现的;
同步代码块:monitorenter插⼊到同步代码块开始位置,monitorexit指令插⼊到同步代码块结束的位置,任何对象都有⼀个monitor与之关联,当且⼀个monitor被持有了之后,他将处于锁定的状态,当线程执⾏到monitorenter的地⽅时,他将会尝试获取对象锁持有的monitor所有权,即获取对象的锁;
monitorenter:每个对象都是⼀个监视器锁(monitor),当monitor被占⽤时就会处于锁定状态,线程执⾏monitorenter时尝试获取monitor的所有权,过程如下:
1.如果monitor的进⼊数为0,则该线程进⼊monitor,然后将进⼊数置为1,该线程即为monitor的持有者;
2.如果线程已经占有该锁,只是重新进⼊,则将monitor的进⼊数加1;
3.如果其他线程已经占有了该锁,那么该线程就处于阻塞状态,直到monitor的进⼊数为0,则将再次尝试获取该锁的所有权;
monitorexit:执⾏monitorexit的线程必须是对象所对应的monitor的持有者,指令执⾏时,monitor的进⼊数减1,直到monitor的进⼊数为0时,他将不再是该对象的monitor的持有者,其他被这个monitor阻塞的线程将尝试获取该monitor的所有权;
对于⽅法来说,⽅法的同步并没有通过monitorenter和monitorexit来实现(理论上也是通过这两个指令来实现的),相⽐与普通⽅法,其常量池中多了ACC_SYNCHRONIZED标志符;
⽅法调⽤时,调⽤指令会先检测⽅法的ACC_SYNCHRONIZED访问标志有没有被设置,如果设置了,执⾏线程将会获取monitor,获取成功之后才可以执⾏⽅法体,⽅法执⾏完之后会释放monitor,在⽅法执⾏期间,任何其他线程将不能获取到同⼀个monitor对象;
两种同步⽅式本质上没有区别,只是⽅法的同步是⼀种隐式的⽅式来实现,⽆需通过字节码来完成。两个指令的执⾏是JVM通过调⽤操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“⽤户态和内核态”两个态之间来回切换,对性能有较⼤影响。
synchronized 三种使⽤⽅式(修饰实例、静态⽅法、静态代码块):
修饰实例⽅法: 作⽤于当前对象实例加锁,进⼊同步代码前要获得当前对象实例的锁
修饰静态⽅法: 也就是给当前类加锁,会作⽤于类的所有对象实例,因为静态成员不属于任何⼀个实
例对象,是类成员( static 表明这是该类的⼀个静态资源,不管new了多少个对象,只有⼀份)。所以如果⼀个线程A调⽤⼀个实例对象的⾮静态 synchronized ⽅法,⽽线程B需要调⽤这个实例对象所属类的静态 synchronized ⽅法,是允许的,不会发⽣互斥现象,因为访问静态 synchronized ⽅法占⽤的锁是当前类的锁,⽽访问⾮静态 synchronized ⽅法占⽤的锁是当前实例对象锁。
修饰代码块: 指定加锁对象,对给定对象加锁,进⼊同步代码库前要获得给定对象的锁。
总结: synchronized 关键字加到 static 静态⽅法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例⽅法上是给对象实例上锁。尽量不要使⽤ synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!
多线程中 synchronized 锁升级的原理是什么?
在锁对象的对象头⾥⾯有⼀个 threadid 字段,在第⼀次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程id,再次进⼊的时候会先判断 threadid 是否与其线程 id ⼀致,如果⼀致则可以直接使⽤此对象,如果不⼀致,则升级偏向锁为轻量级锁,通过⾃旋循环⼀定次数来获取锁,执⾏⼀定次数之后,如果还没有正常获取到要使⽤的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
锁的升级的⽬的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现⽅式,使⽤了偏向锁升级为轻量级锁再升级到重量级锁的⽅式,从⽽减低了锁带来的性能消耗。
synchronized 锁升级的过程
偏向锁 → 轻量级锁(⾃旋锁) → 重量级锁知道的英文
1.6 之前,⾃旋超过 10次 或者 等待的线程超过cpu核数的1/2,就升级成重量级锁
线程 B 怎么知道线程 A 修改了变量
(1)volatile 修饰变量
(2)synchronized 修饰修改变量的⽅法
(3)wait/notify
(4)while 轮询
synchronized 和 Lock 有什么区别?
⾸先synchronized是Java内置关键字,在JVM层⾯,Lock是个Java类;
synchronized 可以给类、⽅法、代码块加锁;⽽ lock 只能给代码块加锁。
synchronized 不需要⼿动获取锁和释放锁,使⽤简单,发⽣异常会⾃动释放锁,不会造成死锁;⽽ lock 需要⾃⼰加锁和释放锁,如果使⽤不当没有 unLock()去释放锁就会造成死锁。
通过 Lock 可以知道有没有成功获取锁,⽽ synchronized 却⽆法办到。
synchronized 和 ReentrantLock 区别是什么?
synchronized 是和 if、el、for、while ⼀样的关键字,ReentrantLock 是类,这是⼆者的本质区别。既然 ReentrantLock 是类,那么它就提供了⽐synchronized 更多更灵活的特性,可以被继承、可以有⽅法、可以有各种各样的类变量
synchronized 早期的实现⽐较低效,对⽐ ReentrantLock,⼤多数场景性能都相差较⼤,但是在 Java 6 中对 synchronized 进⾏了⾮常多的改进。
相同点:两者都是可重⼊锁
两者都是可重⼊锁。“可重⼊锁”概念是:⾃⼰可以再次获取⾃⼰的内部锁。⽐如⼀个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如
果不可锁重⼊的话,就会造成死锁。同⼀个线程每次获取锁,锁的计数器都⾃增1,所以要等到锁的计数器下降为0时才能释放锁。
evernote是什么主要区别如下: