Java 并发基础常见面试题总结
什么是线程和进程
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。
public class MultiThread {
public static void main(String[] args) {
ThreadMXBean threadMXBean= ManagementFactory.getThreadMXBean();
//获取java线程管理MXBean
ThreadInfo[] threadInfos=threadMXBean.dumpAllThreads(false,false);
for (ThreadInfo threadInfo:threadInfos){
System.out.println("["+threadInfo.getThreadId()+"]"+threadInfo.getThreadName());
}
}
}
上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):
[5] Attach Listener //添加事件
[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
[3] Finalizer //调用对象 finalize 方法的线程
[2] Reference Handler //清除 reference 线程
[1] main //main 线程,程序入口
为什么程序计数器、虚拟机栈和本地方法栈是线程私有的呢?为什么堆和方法区是线程共享的呢?
程序计数器主要有下面两个作用:
1.字节码解释器通过改变程序计数器依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
2.在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。
保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。
堆和方法区
堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,放新创建的对象,方法区主要用于存放被加载的类信息、常量、静态变量、即时编译器编译的代码等数据。
并发: 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
并行: 单位时间内,多个任务同时执行。
当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换会这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
如何避免死锁
学过操作系统的朋友都知道产生死锁必须具备以下四个条件:
互斥条件:该资源任意一个时刻只由一个线程占用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
破坏互斥条件
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
破坏请求与保持条件
一次性申请所有的资源。
破坏不剥夺条件
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
说说 sleep() 方法和 wait() 方法区别和共同点?
两者最主要的区别在于:sleep 方法没有释放锁,而 wait 方法释放了锁 。
两者都可以暂停线程的执行。
Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。
为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
调用start可以启动线程并使线程进入就绪状态,run方法只是thread的一个普通方法调用,还是在主线程执行的。
上一篇: C++常见面试题总结(1)
下一篇: MyBatis常见面试题总结