Java多线程(面试问题)
进程和线程的区别
进程:进程独占内存空间,保存各自运行状态,相互间不干扰且可以互相切换,为并发处理任务提供了可能
线程:共享进程的内存资源,相互间切换更快速,支持更细力度的任务控制,使进程内的子任务得以并发执行
进程是资源分配的最小单位,线程是CPU调度的最小单位
- 所有与进程相关的资源,都被记录在进程控制块(PCB)中
- 进程是抢占处理器的调度单位;线程属于某个进程,共享其资源
- 线程只由堆栈寄存器、程序计数器等组成
主要区别:
- 线程不能看作独立应用,而进程可以看作为独立应用
- 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径
- 线程没有独立的地址空间,多进程的程序比多线程更健壮
- 进程的切换比线程的切换开销大
java进程和线程的关系:
- Java对操作系统提供的功能进行封装,包括进程和线程
- 运行一个程序会产生一个进程,进程包含至少一个线程
- 每个进程对应一个JVM实例,多个线程共享JVM里的堆
- Java采用单线编程模型,程序会自动创建主线程
- 主线程可以创建子线程,原则上要后于子线程完成执行(需要执行各种关闭动作)
进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。
线程与进程的区别归纳:
a.地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
b.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
c.调度和切换:线程上下文切换比进程上下文切换要快得多。
d.在多线程OS中,进程不是一个可执行的实体。
进程是运行中的程序,线程是进程的内部的一个执行序列
进程是资源分配的单元,线程是执行单元
进程间切换代价大,线程间切换代价小
进程拥有资源多,线程拥有资源少
多个线程共享进程的资源
有4种方式可以用来创建线程:
- 继承Thread类
- 实现Runnable接口
- 应用程序可以使用Executors框架来创建线程池
- 实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。
- 还有一种方式是实现Callable接口
线程的start()和run()方法的区别
调用关系:
Thread#start() -> JVM_StartThread -> thread_entry(创建新的子线程) -> Thread#run()
- 调用start()方法会创建一个新的子线程并启动
- run()方法只是Thread的一个普通方法的调用
Thread和Runnable的关系
- Thread是实现了Runnable接口的类,使得run支持多线程
- 因为Java类的单一继承原则,推荐使用Runnable接口
如何实现处理线程的返回值
- 主线程等待法
- 使用Thread类的join()阻塞当前线程以等待子线程处理完毕
- 通过Callable接口实现:通过FutureTask Or 线程池获取
1 | public class MyCallable implements Callable<String> { |
线程的状态(6个状态)
- 新建(New):创建后尚未启动的线程的状态
- 运行(Runnable):包含Running和Ready
- 无限期等待(Waiting):不会被分配CPU执行时间,需要显示被唤醒
- 没有设置Timeout参数的Object.wait()方法
- 没有设置Timeout参数的Thread.join()方法
- LockSupport.park()方法
- 限期等待(Timed Waiting):在一定时间后会由系统自动唤醒
- Thread.sleep()方法
- 设置Timeout参数的Object.wait()方法
- 设置Timeout参数的Thread.join()方法
- LockSupport.parkNanos()方法
- LockSupport.parkUntil()方法
- 阻塞(Blocked):等待获取排它锁(synchronized)
- 结束(terminated):已终止线程的状态,线程已经结束执行
- 在一个终止的线程调用start()方法,会抛出java.lang.IllegalThreadStateException
sleep()和wait()的区别
基本差别:
- sleep()是Thread类的方法,wait()是Object类的方法
- sleep()方法可以在任何地方使用
- wait()方法只能在synchronized方法/synchronized块中使用
本质区别:
- Thread.sleep()只会让出CPU,不会释放锁(不会导致锁行为的改变)
- Object.wait()不仅让出CPU,还会释放已经占有的同步锁资源
notify()和notifyAll()的区别
两个概念:锁池EntryList 、 等待池WaitSet
锁池(未获得锁的线程等待的地方):假设线程A已经拥有了某个对象(不是类)的锁,而其他线程B、C想要调用这个对象的某个synchronized方法(或者块),由于B、C线程在进入对象synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方区等待锁的释放,这个地方便是该对象的锁池。
等待池:假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。
区别
- notifyAll()会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会
- notify()只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
yield()方法的功能
- 当前线程可以让出CPU使用权,但是线程调度器可能会忽略这个暗示
- 同sleep()一样,yield()不会让出当前正在占用的锁
interrupt()方法
- 调用interrupt()方法,将线程的中断标志设置为true
- 如果线程处于被阻塞状态,那么线程立即退出被阻塞状态,并抛出InterruptedException异常
- 如果线程处于正常活动状态,则只将中断标志设置为true,线程正常运行,不受影响
- 需要被调用的线程配合中断(while 检查isInterrupted())
- 在正常运行任务是,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
- 需要被调用的线程配合中断(while 检查isInterrupted())
isInterrupted() 和 interrupted()
isInterrupted()
- 检测调用该方法的线程是否被中断,中断不会被清除。一旦sleep等方法抛出异常,它将清除中断状态
1 | public boolean isInterrupted() { |
interrupted()
1 | public static boolean interrupted() { |
- 检测当前线程是否被中断,并清除中断状态
- 静态方法,不能再特定线程上使用,只能报告调用它的现成的中断状态
- 线程不存活时,返回false