Bootstrap

Java面试题经典面试题220道(附答案)

1.线程的生命周期?线程有几种状态?

线程通常有五种状态,创建,就绪,运⾏、阻塞和死亡状态:

        1. 新建状态(New) :新创建了一个线程对象。

        2. 就绪状态(Runnable) :线程对象创建后,其他线程调⽤了该对象的start⽅法。该状态的线程位于可运⾏线程池中,变得可运行,等待获取CPU的使⽤权。

        3. 运⾏状态(Running) :就绪状态的线程获取了CPU,执⾏程序代码。

        4. 阻塞状态(Blocked) :阻塞状态是线程因为某种原因放弃CPU使⽤权,暂时停止运行。直到线程进⼊就绪状态,才有机会转到运行状态。

        5. 死亡状态(Dead) :线程执行完了或者因异常退出了run⽅法,该线程结束⽣命周期。

阻塞的情况⼜分为三种:

        1. 等待阻塞 :运⾏的线程执行wait方法,该线程会释放占⽤的所有资源,JVM会把该线程放⼊“等待池”中。进⼊这个状态后,是不能自动唤醒的,必须依靠其他线程调⽤notify或notifyAll方法才能被唤醒,wait是object类的方法

        2. 同步阻塞 :运行的线程在获取对象的同步锁时,若该同步锁被别的线程占⽤,则JVM会把该线程放⼊“锁池”中。

        3. 其他阻塞 :运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终⽌或者超时、或者I/O处理完毕时,线程重新转⼊就绪状态。sleep是Thread类的方法。

2.sleep()、wait()、join()、yield()之间的的区别?

锁池:所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进⾏等待,当前⾯的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线 程得到后会进⼊就绪队列进行等待cpu资源分配。

等待池:当我们调⽤wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,⽽notifyAll()是将等待池的所有线程放到锁池当中。

        1. sleep 是 Thread 类的静态本地⽅法,wait 则是 Object 类的本地⽅法。

        2. sleep⽅法不会释放lock,但是wait会释放,而且会加⼊到等待队列中。

  1. sleep就是把cpu的执⾏资格和执⾏权释放出去,不再运⾏此线程,当定时时间结束再取回cpu资源,

  2. 参与cpu的调度,获取到cpu资源后就可以继续运⾏了。⽽如果sleep时该线程有锁,那么sleep不会

  3. 释放这个锁,⽽是把锁带着进⼊了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个

  4. 锁。也就是说⽆法执⾏程序。如果在睡眠期间其他线程调⽤了这个线程的interrupt⽅法,那么这个

  5. 线程也会抛出interruptexception异常返回,这点和wait是⼀样的。

3. sleep⽅法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。

4. sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别⼈中断)。

5. sleep 一般⽤于当前线程休眠,或者轮循暂停操作,wait 则多⽤于多线程之间的通信。

6. sleep 会让出 CPU 执⾏时间且强制上下文切换,而wait 则不⼀定,wait 后可能还是有机会重新竞争到锁继续执⾏的。 sleep()、wait()、join()、yield()之间的的区别。

7. yield()执行后线程直接进⼊就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执⾏资格,所以有可能cpu下次进⾏线程调度还会让这个线程获取到执⾏权继续执行。

8. join()执⾏后线程进⼊阻塞状态,例如在线程B中调用线程A的join(),那线程B会进⼊到阻塞队列,直到线程A结束或中断线程。

 
  1. public static void main(String[] args) throws InterruptedException {

  2. Thread t1 = new Thread(new Runnable() {

  3. @Override

  4. public void run() {

  5. try {

  6. Thread.sleep(3000);

  7. } catch (InterruptedException e) {

  8. e.printStackTrace();

  9. }

  10. System.out.println("22222222");

  11. }

  12. });

  13. t1.start();

  14. t1.join();

  15. // 这⾏代码必须要等t1全部执⾏完毕,才会执⾏

  16. System.out.println("1111");

  17. }

  18. ###执行结果

  19. 22222222

  20. 1111

3.Thread和Runable的区?

        Thread和Runnable的实质是继承关系,没有可比性。⽆论使⽤Runnable还是Thread,都会new  Thread,然后执行run方法。⽤法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行⼀个任务,那就实现runnable。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

 需要全套面试笔记的【点击此处即可】即可免费获取

4.并发、并行、串行之间的区别?

1. 串⾏在时间上不可能发⽣重叠,前⼀个任务没搞定,下⼀个任务就只能等着。

2. 并⾏在时间上是重叠的,两个任务在同⼀时刻互不干扰的同时执行。

3. 并发允许两个任务彼此干扰。统⼀时间点、只有⼀个任务运行,交替执行。

5.并发的三大特性?

        原子性 :原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要 不都不执行。

        可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

        有序性: 虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重排序。

6.Java死锁如何避免?

造成死锁的几个原因:

        1. 一个资源每次只能被一个线程使⽤。

        2. 一个线程在阻塞等待某个资源时,不释放已占有资源。

        3. 一个线程已经获得的资源,在未使用完之前,不能被强行剥夺。

        4. 若干线程形成头尾相接的循环等待资源关系。

这是造成死锁必须要达到的4个条件,如果要避免死锁,只需要不满足其中某一个条件即可。而其中前 3个条件是作为锁要符合的条件,所以要避免死锁就需要打破第4 个条件,不出现循环等待锁的关系。

在开发过程中:

        1. 要注意加锁顺序,保证每个线程按同样的顺序进行加锁。

        2. 要注意加锁时限,可以针对所设置一个超时时间。

        3. 要注意死锁检查,这是一种预防机制,确保在第⼀时间发现死锁并进行解决。

7.为什么用线程池?解释下线程池参数?

用线程池的原因:

1、降低资源消耗;提⾼线程利用率,降低创建和销毁线程的消耗。

2、提⾼响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。

3 、提⾼线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。

线程池参数 :

        1.corePoolSize 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程 。

        2.maxinumPoolSize 代表的是最⼤线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最⼤线程数。

        3.keepAliveTime 、 unit 表示超出核⼼线程数之外的线程的空闲存活时间,也就是核心线程

不会消除,但是超出核心线程数的部分线程如果空闲⼀定的时间则会被消除,我们可以通过

setKeepAliveTime 来设置空闲时间。

        4.workQueue 用 来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部 放⼊队列,直到整个队列被放满但任务还再持续进⼊则会开始创建新的线程。

        5.ThreadFactory 实际上是⼀个线程工厂,⽤来⽣产线程执行任务。我们可以选择使⽤默认的创建工厂,产⽣的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选 择⾃定义线程工厂,一般我们会根据业务来制定不同的线程工厂。

        6.Handler 任务拒绝策略,有两种情况,第⼀种是当我们调⽤ shutdown 等⽅法关闭线程池

后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续

想线程池提交任务就会遭到拒绝。另⼀种情况就是当达到最⼤线程数,线程池已经没有能⼒继续处

理新提交的任务时,这是也就拒绝。

8.线程池的底层⼯作原理

线程池内部是通过队列+线程实现的,当我们利用线程池执行任务时:

       

         1. 如果此时线程池中的线程数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

       

         2. 如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。

         

        3. 如果此时线程池中的线程数量⼤于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量⼩于maximumPoolSize,建新的线程来处理被添加的任务。

        

        4. 如果此时线程池中的线程数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

        

        5. 当线程池中的线程数量⼤于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

9.ReentrantLock中tryLock()和lock()⽅法的区别

        1. tryLock()表示尝试加锁,可能加到,也可能加不到,该方法不会阻塞线程,如果加到锁则返回 true,没有加到则返回false。

        

        2. lock()表示阻塞加锁,线程会阻塞直到加到锁,方法也没有返回值。

10.Sychronized和ReentrantLock的区别

        1. sychronized是一个关键字,ReentrantLock是一个类。

        2. sychronized会自动的加锁与释放锁,ReentrantLock需要程序员手动加锁与释放锁。

        3. sychronized的底层是JVM层⾯的锁,ReentrantLock是API层面的锁。

        4. sychronized是非公平锁,ReentrantLock可以选择公平锁或非公平锁。

        5. sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来标识锁的状态。

        6. sychronized底层有一个锁升级的过程。

11.谈谈你对AQS的理解,AQS如何实现可重入锁?

        1. AQS是⼀个JAVA线程同步的框架。是JDK中很多锁工具的核心实现框架。

        2. 在AQS中,维护了⼀个信号量state和⼀个线程组成的双向链表队列。其中,这个线程队列,就是用来给线程排队的,而state就像是一个红绿灯,用来控制线程排队或者放行的。 在不同的场景下,有不用的意义。

        3. 在可重入锁这个场景下,state就⽤来表示加锁的次数。 0 标识无锁,每加一次锁,state就加 1 。释放锁state就减1 。

12.ThreadLocal的底层原理

        1. ThreadLocal是Java中所提供的线程本地存储机制,可以利用该机制将数据 缓存在某个线程内部 ,该线程可以在任意时刻、任意方法中获取缓存的数据

        2. ThreadLocal底层是通过 ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值

        3. 如果在线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完之后,应该要把设置的key,value,也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,手动调用ThreadLocal的remove⽅法,手动清楚Entry对象。

        4. ThreadLocal经典的应⽤场景就是连接管理(一个线程持有一个连接,该连接对象可以在不同的方法之间进行传递,线程之间不共享同一个连接)。

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

 需要全套面试笔记的【点击此处即可】即可免费获取

;