Java多线程面试知识点总结

mac2024-10-01  30

Java多线程面试知识点总结

2019/10/31 周四


程序、进程、线程、协程的概念区分:

程序:含有指令和数据的文件,存储在磁盘上,是可执行的静态代码进程:内存中正在运行的程序,是应用程序的执行实例,是系统分配资源的独立单位,有自己独立的内存空间线程:进程中执行运算的最小单位,CPU调度和分配的基本单位;不能独立存在,一个进程至少拥有一个线程,同一个进程中的线程共享同一个内存空间;有自己的局部变量协程:更小的线程单位,协程的调度完全由用户控制;一个线程可以拥有多个协程,一个进程也可以单独拥有多个协程进程和线程都是同步机制,协程是异步,能保留上一次调用时的状态

开启线程的方式

继承 java.lang.Thread 类,重写run()方法实现 java.lang.Runnable 接口,重写run()方法,使用时还要new一个Thread类的实例,把Runnable实例作为参数实现 java.util.concurrent.Callable 接口,重写call()方法,会抛出异常,有返回值;必须指定泛型,返回值就是泛型从线程池中获取(注:Thread线程对象不能使用线程池)
Runnable接口和Callable接口的区别:

(1)Runnable中run方法没有返回值,而Callable中call方法有返回值,返回值类型是泛型类型 (2)Callable中的call方法会抛出异常,而Runnable的run方法不会 (3)Callable可以在没有获取到需要的数据的情况下取消线程任务 (4)在线程池中Runnable线程对象用execute()方法执行,Callable对象用submit()方法执行线程(因为有返回值)

线程的生命周期

New-新生状态/初始状态 (用new关键字实例化一个线程)Runnable-就绪状态/可运行状态 (调用t.start()方法)Running-运行状态 (获取到cpu时间片,run()方法正在执行)Blocked-阻塞状态: (1)sleep线程休眠阻塞,休眠结束了阻塞状态就结束了 (2)wait线程等待阻塞,进入等待状态,需要notify或notifyAll唤醒 (3)join阻塞,当前线程调用了其他线程的join方法,等其他线程的join方法结束了,阻塞状态就结束了 (4)IO阻塞,如等待用户在控制台输入时阻塞,用户输入完阻塞结束 (5)yield线程礼让,归还时间片,让CPU重新分配,但还是有可能抢到CPU资源,这种情况就没有阻塞 注意:阻塞状态结束后,线程回到就绪状态,而不是马上到运行状态Dead-死亡状态: (1)线程的run方法执行完成,线程正常销毁 (2)执行run方法时,抛出异常

sleep和wait的区别

sleep是线程Thread类的方法,wait是Object类的方法sleep必须捕获异常,wait不需要sleep可以在任何地方使用,wait需要在同步方法或同步代码块中使用sleep没有释放锁,只是让出了CPU,而wait释放了锁,使其他方法可以使用sleep可以指定休眠时间,时间到了自然醒,而wait需要notify或notifyAll唤醒

线程之间的通信

通过wait()和notify()/notifyAll()等待通知机制 wait()的作用是使当前线程等待,代码停止执行,直到接到通知或被终止;调用wait()之前必须获得对象的锁,即只能在同步方法或代码块中调用wait()方法,执行wait()方法后当前线程释放锁 notify()用来通知等待对象锁的其他线程,使该线程退出等待状态,进入就绪状态;如果有多个线程等待,则由线程规划器随机挑选一个,通知它准备去获取该对象的锁;notify()执行完后,当前线程不会马上释放对象锁,而是要等执行notify()方法的线程执行完使用join()方法 join()方法是主线程创建并启动子线程,在等待子线程执行完或执行一定时间后再执行;可以获取子线程的结果进行运算,以此实现通信;join()的主要作用就是同步,使并行变为串行使用ThreadLocal关键字 线程局部变量,每个线程有自己单独的变量,一般是静态的,在该线程内可以共享,主要解决的是线程之间的隔离性

调用run()方法和start()方法的区别

直接调用run()方法相当于调用了一个普通方法,没有开启新线程,是同步的;而start()才是开启了一个新线程

线程池

线程池就是一个容纳多个线程的容器。因为线程的创建和销毁都需要占用CPU,而线程池中的线程可以反复使用,就省去了反复创建的繁琐步骤,也可以节省CPU资源的损耗,提高性能

四种线程池:
Executors.newSingleThreadExecutor()单个线程池,串行Executors.newFieldThreadPool()固定数量的线程池,达到线程池最大数量后,后面的线程需要排队等待Executors.newCacheThreadPool可缓存线程池,当有新任务需要执行时,会智能添加新线程,当线程池大小超过处理任务所需的线程时,也会回收闲置线程Executors.newScheduledThreadPool()定长的线程池,支持定时及周期性任务

锁的种类

从宏观的角度上分为乐观锁和悲观锁,悲观锁认为读少写多,遇到并发写的可能性比较高,因此每次读写数据都会上锁,synchronized是悲观锁;乐观锁认为读多写少,别人拿到资源不会修改,所以不上锁,但在更新的时候会去判断别人在此期间有没有更新这个数据公平锁和非公平锁,公平锁类似于排队获得锁,整体效率会低一点;非公平锁就是会抢占锁,效率高但可能有的线程一直在等待锁;synchronized是非公平锁,且无法实现公平锁互斥锁与非互斥锁,从悲观锁和乐观锁的概念引申出来的,互斥锁是一次只能有一个线程持有的锁,synchronized是互斥锁可重入锁和不可重入锁(自旋锁),某线程已拥有了某锁同步的代码块,可以再次进入该代码块,synchronized是可重入锁类锁和对象锁,类锁使用字节码文件作为锁,比如锁在静态同步方法上,或者在同步代码块中使用.class;对象锁比如锁在同步方法上,或在方法中使用this来锁

死锁

死锁的原因 竞争系统资源:两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法继续执行。 可能是线程推进顺序不当

产生死锁的条件: (1)互斥条件(一个资源只能被一个进程持有); (2)请求和保持条件(因请求资源而阻塞时,保持已经拥有的资源不放); (3)不剥夺条件(进程获得的资源在没使用完时,其他资源无法对它剥夺占用,只能使用完自己释放); (4)循环等待条件(发生死锁时,必然存在一个进程-资源的环路)

如何避免死锁 (1)线程按照一定的顺序加锁; (2)线程尝试获取锁的时候设置一定的加锁时限,超时则放弃对该锁的请求,并释放已有资源; (3)死锁检测

synchronized与lock的异同

synchronized是java关键字,Lock是一个接口类synchronized是锁在类、方法或代码块上,lock是块范围内的,需要指定起始位置synchronized是JVM执行的,lock通过代码执行synchronized会自动释放锁,lock需要手动释放synchronized无法判断锁的状态,lock可以(即知道有没有获得锁)lock可以让锁的线程响应中断,synchronized不会,会一直等待

synchronized的缺陷

试图获得锁不能设置超时,不能控制阻塞时长不能中断一个正在试图获得锁的线程,效率低无法知道是否成功获得锁

并行与并发

相同点:在感受上都是多个任务同时进行并行是不同代码块同时进行,并发是不同代码块交替执行并行是在多个处理器上同时处理多个任务,并发是在一个处理器上交替执行多个任务
最新回复(0)