当前位置:   article > 正文

火爆大厂的100道Java面试题及答案(2022年整理,持续更新) --多线程并发篇_大厂 java 面试 100 问 附答案和文档

大厂 java 面试 100 问 附答案和文档

前言:这是一个基本涵盖Java初中级大部分核心知识点的面试题集,包含了Java基础、容器、多线程、Spring、SpringBoot、MyBatis、Linux、MySQL、Redis、MongoDB、网络协议、JVM等方向。所有题目都是我亲自整理的。

因为无法生成自动跳转的目录,同时我也按照分类汇总整理成了PDF版,排版上相比更加整齐并且带有书签 阅读起来也比较方便全部。前前后后花费半个月的时间,共计24W字。

收藏的时候,顺便点个赞呗~

收藏的时候,顺便点个赞呗~

收藏的时候,顺便点个赞呗~

1. 多线程有什么用?

一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓"知其然知其所以然","会用"只是"知其然","为什么用"才是"知其所以然",只有达到"知其然知其所以然"的程度才可以说是把一个知识点运用自如。OK,下面说说我对这个问题的看法:

1、发挥多核CPU的优势

随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程"同时"运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。

2、防止阻塞

从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

3、便于建模

这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

2. 多线程和单线程的区别和联系?

**1、**在单核 CPU 中,将 CPU 分为很小的时间片,在每一时刻只能有一个线程在执行,是一种微观上轮流占用 CPU 的机制。

**2、**多线程会存在线程上下文切换,会导致程序执行速度变慢,即采用一个拥有两个线程的进程执行所需要的时间比一个线程的进程执行两次所需要的时间要多一些。

结论:即采用多线程不会提高程序的执行速度,反而会降低速度,但是对于用户来说,可以减少用户的响应时间。

3. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?

线程

与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

程序

是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

进程

是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。

4. 线程的创建方式

方法一:继承Thread类,作为线程对象存在(继承Thread对象)

  1. public class CreatThreadDemo1 extends Thread{
  2. /**
  3. * 构造方法: 继承父类方法的Thread(String name);方法
  4. * @param name
  5. */
  6. public CreatThreadDemo1(String name){
  7. super(name);
  8. }
  9. @Override
  10. public void run() {
  11. while (!interrupted()){
  12. System.out.println(getName()+"线程执行了...");
  13. try {
  14. Thread.sleep(200);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }
  20. public static void main(String[] args) {
  21. CreatThreadDemo1 d1 = new CreatThreadDemo1("first");
  22. CreatThreadDemo1 d2 = new CreatThreadDemo1("second");
  23. d1.start();
  24. d2.start();
  25. d1.interrupt(); //中断第一个线程
  26. }
  27. }

常规方法,不多做介绍了,interrupted方法,是来判断该线程是否被中断。(终止线程不允许用stop方法,该方法不会施放占用的资源。所以我们在设计程序的时候,要按照中断线程的思维去设计,就像上面的代码一样)。

让线程等待的方法

  • Thread.sleep(200); //线程休息2ms
  • Object.wait(); //让线程进入等待,直到调用Object的notify或者notifyAll时,线程停止休眠

方法二:实现runnable接口,作为线程任务存在

  1. public class CreatThreadDemo2 implements Runnable {
  2. @Override
  3. public void run() {
  4. while (true){
  5. System.out.println("线程执行了...");
  6. }
  7. }
  8. public static void main(String[] args) {
  9. //将线程任务传给线程对象
  10. Thread thread = new Thread(new CreatThreadDemo2());
  11. //启动线程
  12. thread.start();
  13. }
  14. }

Runnable 只是来修饰线程所执行的任务,它不是一个线程对象。想要启动Runnable对象,必须将它放到一个线程对象里。

方法三:匿名内部类创建线程对象

  1. public class CreatThreadDemo3 extends Thread{
  2. public static void main(String[] args) {
  3. //创建无参线程对象
  4. new Thread(){
  5. @Override
  6. public void run() {
  7. System.out.println("线程执行了...");
  8. }
  9. }.start();
  10. //创建带线程任务的线程对象
  11. new Thread(new Runnable() {
  12. @Override
  13. public void run() {
  14. System.out.println("线程执行了...");
  15. }
  16. }).start();
  17. //创建带线程任务并且重写run方法的线程对象
  18. new Thread(new Runnable() {
  19. @Override
  20. public void run() {
  21. System.out.println("runnable run 线程执行了...");
  22. }
  23. }){
  24. @Override
  25. public void run() {
  26. System.out.println("override run 线程执行了...");
  27. }
  28. }.start();
  29. }
  30. }

创建带线程任务并且重写run方法的线程对象中,为什么只运行了Thread的run方法。我们看看Thread类的源码,

我们可以看到Thread实现了Runnable接口,而Runnable接口里有一个run方法。 所以,我们最终调用的重写的方法应该是Thread类的run方法。而不是Runnable接口的run方法。

方法四:创建带返回值的线程

  1. public class CreatThreadDemo4 implements Callable {
  2. public static void main(String[] args) throws ExecutionException, InterruptedException {
  3. CreatThreadDemo4 demo4 = new CreatThreadDemo4();
  4. FutureTask<Integer> task = new FutureTask<Integer>(demo4); //FutureTask最终实现的是runnable接口
  5. Thread thread = new Thread(task);
  6. thread.start();
  7. System.out.println("我可以在这里做点别的业务逻辑...因为FutureTask是提前完成任务");
  8. //拿出线程执行的返回值
  9. Integer result = task.get();
  10. System.out.println("线程中运算的结果为:"+result);
  11. }
  12. //重写Callable接口的call方法
  13. @Override
  14. public Object call() throws Exception {
  15. int result = 1;
  16. System.out.println("业务逻辑计算中...");
  17. Thread.sleep(3000);
  18. return result;
  19. }
  20. }

Callable接口介绍:

  1. public interface Callable<V> {
  2. /**
  3. * Computes a result, or throws an exception if unable to do so.
  4. *
  5. * @return computed result
  6. * @throws Exception if unable to compute a result
  7. */
  8. V call() throws Exception;
  9. }

返回指定泛型的call方法。然后调用FutureTask对象的get方法得道call方法的返回值。

方法五:定时器Timer

  1. public class CreatThreadDemo5 {
  2. public static void main(String[] args) {
  3. Timer timer = new Timer();
  4. timer.schedule(new TimerTask() {
  5. @Override
  6. public void run() {
  7. System.out.println("定时器线程执行了...");
  8. }
  9. },0,1000); //延迟0,周期1s
  10. }
  11. }

方法六:线程池创建线程

  1. public class CreatThreadDemo6 {
  2. public static void main(String[] args) {
  3. //创建一个具有10个线程的线程池
  4. ExecutorService threadPool = Executors.newFixedThreadPool(10);
  5. long threadpoolUseTime = System.currentTimeMillis();
  6. for (int i = 0;i<10;i++){
  7. threadPool.execute(new Runnable() {
  8. @Override
  9. public void run() {
  10. System.out.println(Thread.currentThread().getName()+"线程执行了...");
  11. }
  12. });
  13. }
  14. long threadpoolUseTime1 = System.currentTimeMillis();
  15. System.out.println("多线程用时"+(threadpoolUseTime1-threadpoolUseTime));
  16. //销毁线程池
  17. threadPool.shutdown();
  18. threadpoolUseTime = System.currentTimeMillis();
  19. }
  20. }

方法七:利用java8新特性 stream 实现并发

5. 线程有哪些基本状态?

Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态(图源《Java 并发编程艺术》

线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4节):

操作系统隐藏 Java虚拟机(JVM)中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:HowToDoInJava:Java Thread Life Cycle and Thread States),所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。

“ 操作系统隐藏 Java虚拟机(JVM)中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:HowToDoInJava:),所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。

当线程执行 wait()方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。

6. 如何停止一个正在运行的线程

1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。

3、使用interrupt方法中断线程。

  1. class MyThread extends Thread {
  2. volatile boolean stop = false;
  3. public void run() {
  4. while (!stop) {
  5. System.out.println(getName() + " is running");
  6. try {
  7. sleep(1000);
  8. } catch (InterruptedException e) {
  9. System.out.println("week up from blcok...");
  10. stop = true; // 在异常处理代码中修改共享变量的状态
  11. }
  12. }
  13. System.out.println(getName() + " is exiting...");
  14. }
  15. }
  16. class InterruptThreadDemo3 {
  17. public static void main(String[] args) throws InterruptedException {
  18. MyThread m1 = new MyThread();
  19. System.out.println("Starting thread...");
  20. m1.start();
  21. Thread.sleep(3000);
  22. System.out.println("Interrupt thread...: " + m1.getName());
  23. m1.stop = true; // 设置共享变量为true
  24. m1.interrupt(); // 阻塞时退出阻塞状态
  25. Thread.sleep(3000); // 主线程休眠3秒以便观察线程m1的中断情况
  26. System.out.println("Stopping application...");
  27. }
  28. }

7. start()方法和run()方法的区别

只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。

如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。

8. 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

看看Thread的start方法说明哈~

/**     * Causes this thread to begin execution; the Java Virtual Machine     * calls the <code>run</code> method of this thread.     * <p>     * The result is that two threads are running concurrently: the     * current thread (which returns from the call to the     * <code>start</code> method) and the other thread (which executes its     * <code>run</code> method).     * <p>     * It is never legal to start a thread more than once.     * In particular, a thread may not be restarted 
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号