Java并发编程面试题(五万字总结)——快来打怪升级吧

Posted 活跃的咸鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程面试题(五万字总结)——快来打怪升级吧相关的知识,希望对你有一定的参考价值。

并发编程面试题

第一关: 初出茅庐

1.什么是进程?

进程是系统中正在运行的一个程序,程序一旦运行就是进程。

进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。

2.什么是线程?

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

3.线程的实现方式?

1.继承Thread类
2.实现runnable接口
3.实现callable接口
4.线程池

4.线程的状态?

public enum State {
    
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,  
        TERMINATED;
    }
  • NEW状态:new创建一个Thread对象时, 并没处于执行状态,因为没有调用start方法启动改线程,那么此时的状态就是新建状态。
  • RUNNABLE状态:线程对象通过start方法进入runnable状态,启动的线程不一定会立即得到执行,线程的运行与否要看cpu的调度,我们把这个中间状态叫可执行状态(RUNNABLE)。
  • RUNNING状态:一旦cpu通过轮询或其他方式从任务可以执行队列中选中了线程,此时它才能真正的执行自己的逻辑代码。
  • BLOCKED状态:处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
  • TIMED_WAITING状态:处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
  • TERMINATED状态:当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。

5.run方法和start方法的区别

  1. start ()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的star()方法来启动一个线程,这时此线程是处于就绪状态,并没有运行。然后通过此Thread类调用方法run()来完成其运行操作的,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方 法运行结束,此线程终止。然后CPU再调度其它线程。
  2. run () 方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码;程序中只有主线程一这 一个线程,其程序执行路径还是只有一条,这样就没有达到多线程的目的。

6.获取当前线程的名字?

System.out.println(Thread.currentThread().getName());

7.判断线程是否存活?

线程.isAlive();

8.sleep()方法的作用?

方法sleep()的作用是在指定的毫秒数内让当前的“正在执行的线程”休眠(暂停执行)。

9.线程的种类

java中线程分为用户线程和守护线程(GC就是一个守护线程)

守护线程的特点:守护线程是一个比较特殊的线程,主要被用做程序中后台调度以及支持性工作。当Java虚拟机中不存在非守护线程时,守护线程才会随着JVM一同结束工作。

//设置为守护线程
thread.setDaemon(true)

Daemon属性需要再启动线程之前设置,不能再启动后设置。

10.什么是synchronized?

synchronized是java中的一个关键字可以用来修饰方法和变量来保证线程的同步。

普通同步方法一> 锁的是当前实例对象。
静态同步方法一>锁的是当前类的Class对象。
同步方法块一>锁的是synchonized括号里配置的对象。

11.线程的基本方法

线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等。

方法名功能
sleep()强迫一个线程睡眠N毫秒。
isAlive()判断一个线程是否存活。
join()等待线程终止。
activeCount()程序中活跃的线程数。
enumerate()枚举程序中的线程。
currentThread()得到当前线程。
isDaemon()一个线程是否为守护线程。
setDaemon()设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName()为线程设置一个名称。
wait()强迫一个线程等待。
notify()通知一个线程继续运行。
setPriority()设置一个线程的优先级。
getPriority():获得一个线程的优先级。
yieid()yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。

12.为什么 wait 和 notify 方法要在同步块中调用?

Java API 强制要求这样做,如果你不这么做,你的代码会抛出
IllegalMonitorStateException 异常。还有一个原因是为了避免 wait 和 notify之间产生竞态条件。

当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。

13.怎么检测一个线程是否拥有锁?

在 java.lang.Thread 中有一个方法叫 holdsLock(),它返回 true 如果当且仅当当前线程拥有某个具体对象的锁。

14.volatile 变量和 atomic 变量有什么不同?

Volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用 volatile 修饰 count 变量那么 count++ 操作就不是原子性的。

而 AtomicInteger 类提供的 atomic 方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

15.为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?

Java 的每个对象中都有一个锁(monitor,也可以成为监视器) 并且 wait(),notify()等方法用于等待对象的锁或者通知其他线程对象的监视器可用。

在 Java 的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是 Object 类的一部分,这样 Java 的每一个类都有用于线程间通信的基本方法。

16.为什么 Thread 类的 sleep()和 yield ()方法是静态的?

Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

17.并发编程三要素?

  1. 原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。
  2. 可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。
  3. 有序性,即程序的执行顺序按照代码的先后顺序来执行。

18.Executors 类是什么?

Executors 为 Executor,ExecutorService,ScheduledExecutorService,ThreadFactory 和 Callable 类提供了一些工具方法。Executors 可以用于方便的创建线程池

第二关:小试牛刀

1.如何优雅的设置睡眠时间

TimeUnit.HOURS.sleep(3);
TimeUnit.MINUTES.sleep(22);
TimeUnit.SECONDS.sleep(55);
TimeUnit.MILLISECONDS.sleep(899);

2.如何停止一个线程

1.使用退出标志使线程正常退出

public class ThreadSafe extends Thread {

 public volatile boolean exit = false; 
 
   public void run() { 
    while (!exit){
        //do something
    }
  } 
}

2.使用stop方法不过该方法已经被标记为过时的方法(因为会造成死锁)

3.使用interrupt()方法中断线程

public class TestThread {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                if (Thread.interrupted()) {
                    System.out.println("线程被停止了,我要退出");
                    try {
                        throw new InterruptedException();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        System.out.println("线程已经被停止了");
                    }
                }
            }
        }, "");
        thread.start();
         try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        thread.interrupt();

    }
}

3.yield()方法和join()的作用

yield()方法

放弃当前cpu资源,将它让给其他的任务占用cpu执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得cpu时间片。

join()方法

join是指把指定的线程加入到当前线程,比如join某个线程a, 会让当前线程b进入等待,直到a的生命周期结束,此期间b线程是处于blocked状态。

public class TestThread {
    public static void main(String[] args) throws Exception{
        Thread thread = new Thread(() -> {
          try {
               try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
          }finally {
              System.out.println(Thread.currentThread().getName());
          }
        }, "a");
        thread.start();
        Thread.currentThread().join();
        System.out.println("等待a线程执行完成才会执行");

    }
}

4.线程的优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到cpu资源比较多,也就是cpu有限执行优先级较高的线程对象中的任务,但是不能保证一定 优先级高,就先执行。

Java的优先级分为1~ 10个等级,数字越大优先级越高,默认优先级大小为5。超出范围则抛出:

java.lang. IlegalArgumentException.

线程的优先级具有继承性,比如a线程启动b线程,b线程与a优先级是一样的。

5.interrupted方法和isInterrupted方法的区别?

//该方法是判断当前线程是否中断(即执行该方法的线程)
 public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
//该方法是指this关键字所在类的对象是否中断
public boolean isInterrupted() {
        return isInterrupted(false);
    }

举一个例子

ThreadA threadA=new ThreadA();
threadA.interrupt();
System.out.println(threadA.interrupted());//false 判断的是主线程main
System.out.println(threadA.isInterrupted());//true 判断的是threadA线程

6.Java虚拟机退出时Daemon线程中的finally块一定会执行吗?

public class TestThread {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
          try {
               try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
          }finally {
              System.out.println(Thread.currentThread().getName());
          }
        }, "aaaa");

        thread.setDaemon(true);
        thread.start();
    }
}

控制台没有任何输出说明finally中的语句没有执行

7.设置线程上下文类加载器

public void setContextClassLoader(ClassLoader cl)
public ClassLoader getContextClassLoader() 

8.什么是原子操作?

不可中断的一个或一系列操作

8.并发和并行

  • 并发:一个处理器同时处理多个任务。

  • 并行:多个处理器或者是多核的处理器同时处理多个不同的任务.前者是逻辑上的同时发生(simultaneous),而后者是物理上的同时发生.

并发性(concurrency),又称共行性,是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。

并行(parallelism)是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。

来个比喻:并发和并行的区别就是一个人同时吃三个馒头和三个人同时吃三个馒头。

9.什么是多线程中的上下文切换?

多线程会共同使用一组计算机上的 CPU,而线程数大于给程序分配的 CPU 数量时,为了让各个线程都有执行的机会,就需要轮转使用 CPU。不同的线程切换使用 CPU发生的切换数据等就是上下文切换。

10.死锁与活锁的区别,死锁与饥饿的区别?

死锁: 是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

在这里插入代码片

产生死锁的必要条件:

1、互斥条件:所谓互斥就是进程在某一时间内独占资源。
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3、不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。 4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

活锁: 任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。

活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。

饥饿: 一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。

Java 中导致饥饿的原因:

  1. 高优先级线程吞噬所有的低优先级线程的 CPU 时间。
  2. 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
  3. 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法),因为其他线程总是被持续地获得唤醒。

11.Java 中用到的线程调度算法是什么?

采用时间片轮转的方式。可以设置线程的优先级,会映射到下层的系统上面的优先级上,如非特别需要,尽量不要用,防止线程饥饿。

12.什么是线程组,为什么在 Java 中不推荐使用?

ThreadGroup 类,可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程,这样的组织结构有点类似于树的形式。

为什么不推荐使用?因为使用有很多的安全隐患吧,没有具体追究,如果需要使用,推荐使用线程池。

13.sleep 与 wait 的区别

  1. 对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于Object 类中的。

  2. sleep()方法导致了程序暂停执行指定的时间,让出 cpu执行其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。

  3. 在调用 sleep()方法的过程中,线程不会释放对象锁。

  4. 而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

14.Java后台线程

  1. 定义:守护线程–也称“服务线程”,他是后台线程,它有一个特性,即为用户线程提供公共服务,在没有用户线程可服务时会自动离开。
  2. 优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
  3. 设置:通过 setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程的方式是在 线程对象创建 之前 用线程对象的 setDaemon 方法。
  4. 在 Daemon 线程中产生的新线程也是 Daemon 的。
  5. 线程则是 JVM 级别的,以 Tomcat 为例,如果你在 Web 应用中启动一个线程,这个线程的生命周期并不会和 Web 应用程序保持同步。也就是说,即使你停止了 Web 应用,这个线程依旧是活跃的。
  6. example: 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是 JVM 上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
  7. 生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。当 JVM 中所有的线程都是守护线程的时候,JVM 就可以退出了;如果还有一个或以上的非守护线程则 JVM 不会退出。

15.死锁案例和死锁分析

public class TestDeadLock {
    public static void main(String[] args) {
        MyResources resources = new MyResources();
        new Thread(()->{
            resources.printA();
        },"A").start();

        new Thread(()->{
            resources.printB();
        },"B").start();

    }
}
class MyResources{
    public String A="A";
    public String B="B";

    public void printA(){
        synchronized (this.A){
            System.out.println(Thread.currentThread().getName()+"\\t 输出AA");
             try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
             synchronized (this.B){
                 System.out.println(Thread.currentThread().getName()+"\\t 输出AA");
             }
        }
    }
    public void printB(){
        synchronized (this.B){
            System.out.println(Thread.currentThread().getName()+"\\t 输出BB");
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            synchronized (this.A){
                System.out.println(Thread.currentThread().getName()+"\\t 输出BB");
            }
        }
    }
}

分析:jps -l+jstack

C:\\Users>jps -l
12516
30168 chapter10.TestDeadLock
33128 org.jetbrains.jps.cmdline.Launcher
34860 jdk.jcmd/sun.tools.jps.Jps

C:\\Users>jstack 30168
2021-09-12 09:46:14
Full thread dump Java HotSpot(TM) 64-Bit Server VM (11.0.10+8-LTS-162 mixed mode):
Found one Java-level deadlock:
=============================
"A":
  waiting to lock monitor 0x000001b9aa5a2980 (object 0x0000000089f95e08, a java.lang.String),
  which is held by "B"
&#

以上是关于Java并发编程面试题(五万字总结)——快来打怪升级吧的主要内容,如果未能解决你的问题,请参考以下文章

❤️五万字❤️离职后一天4面,总结了204道高频Java面试题,已拿阿里offer(建议收藏)

五万字,57道hadoop大厂高频面试题,每一字都细心打磨,强烈建议收藏!

五万字长文:Java面试知识点总结

练手级计算机,快来打怪

我把面试问烂了的⭐Java并发编程⭐总结了一下(带答案,万字总结,精心打磨,建议收藏)

五万字142道超全前端面试题---送给在校招的你