Java多线程面试题及答案(19道最常问必考题)

Java多线程面试题及答案(19道最常问必考题)-mikechen

Java多线程是Java面试题经常被问到的,下面重点详解Java多线程面试题,会包含19道必考的Java多线程面试题。

1.进程、线程的区别?

进程

在操作系统中运行的程序就是进程,比如:你的QQ、播放器、IDE等等。

线程

Java多线程面试题及答案(19道最常问必考题)-mikechen

一个进程可以有多个线程,如视频中同时:听声音、看图像、看弹幕等等,线程更“轻量级”。

 

2.线程有哪几种状态?

线程的状态图,如下所示:

Java多线程面试题及答案(19道最常问必考题)-mikechen

会包含,如下状态:

  • 1)新建状态;
  • 2)就绪状态;
  • 3)运行状态;
  • 4)阻塞状态;
  • 5)死亡状态;

 

3.实现线程有哪些方式?

常用的线程创建方式,会包含如下3种:

  • 1)采用实现Runnable、Callable接口的方式创建多线程;
  • 2)使用继承Thread类的方式创建多线程;
  • 3)使用ExecutorService、Future实现有返回结果的多线程;

 

4.Runnable和Callable的区别?

主要会包含如下3点区别:

  •  Callable规定重写的方法是call(),Runnable规定重写的方法是run();
  •  Callable的任务执行后可返回值,而Runnable的任务是不能返回值的;
  •  Call方法可以抛出异常,run方法不可以;

 

5.什么是线程池? 

线程池,英语:thread pool,是一种池化技术。

线程池可以通过提前创建若干个线程,如果有任务需要处理,直接从线程池里拿线程来处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。

 

6.为什么需要线程池?

由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候,就可以考虑使用线程池来提升系统的性能。

主要体现在如下2个方面:

1)重用存在的线程,减少对象创建销毁的开销;

2)可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞;

 

7.线程池有哪几种创建方式?

java 提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。

常见的线程池的创建,会包含如下有7种:

1)newCachedThreadPool:创建一个可缓存线程池;

2)newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数;

3)newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行;

4)newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务;

5)Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;

6)Executors.newWorkStealingPool:创建一个抢占式执行的线程池;

7)ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数;

 

8.线程池的核心参数有哪些?

线程池的核心参数,会包含如下几种,如下图所示:

Java多线程面试题及答案(19道最常问必考题)-mikechen

 

9.线程池的执行流程?

线程池的执行流程,如下图所示:

Java多线程面试题及答案(19道最常问必考题)-mikechen
线程池的执行流程,会包含如下步骤:
  1. 从上图可以看出,提交任务之后,首先会尝试着交给核心线程池中的线程来执行;
  2. 但是必定核心线程池中的线程数有限,所以必须要由任务队列来做一个缓存,先将任务放队列中缓存,然后等待线程去执行;
  3. 最后由于任务太多,队列也满了,这个时候线程池中剩下的线程,就会启动来帮助核心线程池执行任务;
  4. 如果还是没有办法正常处理新到的任务,则线程池只能将新提交的任务交给饱和策略来处理了;

 

10.什么是并行?

并发,如下图所示:

Java多线程面试题及答案(19道最常问必考题)-mikechen
并行:指的是多个cpu实例,或者多台机器,同时执行一段处理逻辑,是真正的同时。

 

11.什么是并发?

并发,如下图所示:

Java多线程面试题及答案(19道最常问必考题)-mikechen

并发: 指单个cpu同时处理多个线程任务,通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。

 

12.并发三要素有哪些?实现原理?

1)原子性:指的是一个或者多个操作,要么全部执行,要么就全部都不执行,所有操作是一个。

举个经典的简单例子,银行转账,A 像 B 转账 100 元。

转账这个操作其实包含两个离散的步骤:

  • 步骤 1:A 账户减去 100;
  • 步骤 2:B 账户增加 100;

我们要求转账这个操作是原子性的,也就是说步骤 1 和步骤 2 是顺续执行且不可被打断的,要么全部执行成功、要么执行失败。

 

2)可见性:指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。

如下图所示:

Java多线程面试题及答案(19道最常问必考题)-mikechen

可见性的方法有:volatile、synchronized、AtomicXXX等等。

volatile这种方式可以保证每次取数直接从主存取,它只能保证内存的可见性,无法保证原子性。

Synchronized 能够实现原子性和可见性,在 Java 内存模型中,synchronized规定线程在加锁时,先清空工作内存,然后在主内存中拷贝最新变量的副本到工作内存 ,然后将更改后的共享变量的值刷新到主内存中 ,最后释放互斥锁。

synchronized保证同一个时刻只有一个线程获取锁执行代码,锁释放之前把最新的值刷新到主内存,实现可见性。

 

3)有序性:指的是在多线程环境下,由于执行语句重排序后,重排序的这一部分没有一起执行完,就切换到了其它线程,导致的结果与预期不符的问题。

有序性是编译器的编译优化,给并发编程带来的程序有序性问题。

 

13.什么是同步?

Java中的同步指的是:通过人为的控制和调度,保证共享资源的多线程访问成为线程安全。

线程的同步是为了防止,多个线程访问一个数据对象时,对数据造成的破坏,线程的同步是保证多线程安全访问竞争资源的一种手段。

 

14.线程的同步方式有哪些?

可以使用synchronized关键字来实现多线程同步,保证共享资源的安全。

比如:

 

/**
* 用在普通方法
*/
private synchronized void synchronizedMethod() {
System.out.println("--synchronizedMethod start--");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--synchronizedMethod end--");
}

锁是当前实例对象 ,进入同步代码前要获得当前实例的锁。

也可以使用ReentrantLock来实现线程同步,当然Atomic等也可以实现线程同步。

 

15.线程状态是如何流转的?

线程流程状态转换,如下图所示:

 

Java多线程面试题及答案(19道最常问必考题)-mikechen

可以对照上面的线程状态流转图来看具体的方法,这样更清楚具体作用:

1.start():启动当前线程, 调用当前线程的run()方法;

2.run():通常需要重写Thread类中的此方法;

3.yield():释放当前CPU的执行权;

4.join():在线程a中调用线程b的join(), 此时线程a进入阻塞状态,;

5.sleep:让线程睡眠指定的毫秒数,在指定时间内线程是阻塞状态;

6.wait():一旦执行此方法当前线程就会进入阻塞,一旦执行wait()会释放同步监视器;

7.notify():一旦执行此方法,将会唤醒被wait的一个线程;

8.LockSupport:LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的;

 

16.sleep()和wait()的区别?

1) 两个方法声明的位置不同

Thread类中声明sleep(),Object类中声明wait()。

2) 调用要求不同

sleep()可以在任何需要的场景下调用,wait()必须在同步代码块中调用。

3) 关于是否释放同步监视器

如果两个方法都使用在同步代码块呵呵同步方法中,sleep不会释放锁,wait会释放锁。

 

17.synchronized的底层实现?

synchronized的底层实现是完全依赖JVM虚拟机的,所以谈synchronized的底层实现,就不得不谈数据在JVM内存的存储:Java对象头,以及Monitor对象监视器。

1.Java对象头

Java对象头主要包括两部分数据:

Java多线程面试题及答案(19道最常问必考题)-mikechen

1)类型指针(Klass Pointer)

是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;

2)标记字段(Mark Word)

用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关键.

所以,很明显synchronized使用的锁对象是存储在Java对象头里的标记字段里。

2.Monitor

下图是synchronized同步代码块反编译后的截图,可以很清楚的看见monitor的调用。

Java多线程面试题及答案(19道最常问必考题)-mikechen

monitor描述为对象监视器,可以类比为一个特殊的房间,这个房间中有一些被保护的数据,进入房间即为持有monitor,退出房间即为释放monitor。

 

18.多线程引入问题有哪些?

多线程的优点很明显,但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来,上下文切换的额外负担,并且线程间的共享变量可能造成死锁的出现。

 

19.什么是线程死锁?如何避免?

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁,如图所示:

Java多线程面试题及答案(19道最常问必考题)-mikechen

举一个例子:

public void add(int m) {
    synchronized(lockA) { // 获得lockA的锁
        this.value += m;
        synchronized(lockB) { // 获得lockB的锁
            this.another += m;
        } // 释放lockB的锁
    } // 释放lockA的锁
}

public void dec(int m) {
    synchronized(lockB) { // 获得lockB的锁
        this.another -= m;
        synchronized(lockA) { // 获得lockA的锁
            this.value -= m;
        } // 释放lockA的锁
    } // 释放lockB的锁
}

两个线程各自持有不同的锁,然后各自试图获取对方手里的锁,造成了双方无限等待下去,这就是死锁。

以上就是Java多线程面试题及答案详解,更多的Java面试题,请查看:1000+Java面试题及答案详解

陈睿mikechen

10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

关注「mikechen」公众号,获取更多技术干货!

后台回复面试即可获取《史上最全阿里Java面试题总结》,后台回复架构,即可获取《阿里架构师进阶专题全部合集

评论交流
    说说你的看法