Java线程面试题(超高频 超详细!!)

目录

1.如何创建线程?哪种好?

2.线程状态

3.一般线程和守护线程的区别

4.sleep wait yield notify notifyAll join详解

一.Sleep 与 wait 区别

二 yield join notify notifyAll

5.中断线程

6. 多线程如何避免死锁

一.什么是死锁?

二.如何确保 N 个线程可以访问 N 个资源同时又不导致死锁?

7 .多线程的好处以及问题

8.线程通信方式

9.线程中抛出异常怎么办


1.如何创建线程?哪种好?

  有 4 种方式可以用来创建线程
1. 继承 Thread
2. 实现 Runnable 接口
3. 应用程序可以使用 Executor 框架来创建线程池
4. 实现 Callable 接口
实现 Runnable 接口比继承 Thread 类所具有的优势
1 ):适合多个相同的程序代码的线程去处理同一个资源
2 ):可以避免 java 中的单继承的限制
3 ):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4 ):线程池只能放入实现 Runable 或 callable 类线程,不能直接放入继承 Thread 的类
5 ): runnable 实现线程可以对线 程进行复用,因为 runnable 是轻量级的对象,重复 new 不会耗           费太大资源,而 Thread 则不然,它是重量级对象,而且线程执行完就完了,无法再次利用

2.线程状态

Java线程面试题(超高频 超详细!!)_第1张图片

 

1 、新建状态( New ):新创建了一个线程对象。
2 、就绪状态( Runnable ):线程对象创建后,其他线程调用了该对象的 start() 方法。
     该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
3 、运行状态( Running ):就绪状态的线程获取了 CPU ,执行程序代码。
4 、阻塞状态( Blocked ):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止
     运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种
(一)、等待阻塞:运行的线程执行 wait() 方法, JVM 会把该线程放入等待池中。 (wait
              会释放持有的锁 )
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,
              则 JVM 会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行 sleep() join() 方法,或者发出了 I/O 请求时, JVM
              会把该线程置为阻塞状态。当 sleep() 状态超时、 join() 等待线程终止或者超时、或者 I/O                    处理完毕时,线程重新转入就绪状态。(注意,sleep 是不会释放持有的锁)
5 、死亡状态( Dead ):线程执行完了或者因异常退出了 run() 方法,该线程结束生命
      周期。

3.一般线程和守护线程的区别

        所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此, 当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
区别
        唯一的区别是判断虚拟机 (JVM) 何时离开, Daemon 是为其他线程提供服务, 如果全部的 User Thread 已经撤离, Daemon 没有可服务的线程, JVM 撤离。也可以理解为守护线程是 JVM 自动创建的线程(但不一定),用户线程是程序创建的线程;比如 JVM 的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时, Java 虚拟机会自动离开。
在使用守护线程时需要注意一下几点
(1) thread.setDaemon(true) 必须在 thread.start() 之前设置,否则会跑出一个
     IllegalThreadStateException 异常。你不能把正在运行的常规线程设置为守护线程。
(2) Daemon 线程中产生的新线程也是 Daemon 的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一
     个操作的中间发生中断。

4sleep wait yield notify notifyAll join详解

一.Sleep 与 wait 区别

1. sleep 是线程类( Thread )的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。 sleep() 使当前线程进入阻塞状态,在指定时间内不会执行。
2. wait Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待 此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll )后本线程才进入对象锁定池准备获得对象锁进入运行状态。
区别比较
1 、这两个方法来自不同的类分别是 Thread Object
2 、最主要是 sleep 方法没有释放锁,而 wait 方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3 wait notify notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可 以在任何地方使用( 使用范围 )
4 sleep 必须捕获异常,而 wait notify notifyAll 不需要捕获异常
(1)
        sleep 方法属于 Thread 类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程 的运行也需要间,一个线程对象调用了 sleep 方法之后,并不会释放他所持有的所有对象 锁,所以也就不会影响其他进程对象的运行。但在 sleep 的过程中过程中有可能被其他对象 调用它的 interrupt(), 产生 InterruptedException 异常,如果你的程序不捕获这个异常,线程 就会异常终止,进入 TERMINATED 状态,如果你的程序捕获了这个异常,那么程序就会继 续执行 catch 语句块 ( 可能还有 finally 语句块 ) 以及以后的代码。
        注意 sleep() 方法是一个静态方法,也就是说他只对当前对象有效,通过 t.sleep() t 对象进入 sleep ,这样的做法是错误的,它只会是使当前线程被 sleep 而不是 t 线程
(2)
         wait 属于 Object 的成员方法,一旦一个对象调用了 wait 方法,必须要采用 notify() notifyAll() 方法唤醒该进程 ; 如果线程拥有某个或某些对象的同步锁,那么在调用了 wait() 后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了 wait() 方法的对象。wait()方法也同样会在 wait 的过程中有可能被其他对象调用 interrupt() 方法而产生

二 yield join notify notifyAll

        yield() 方法是停止当前线程,让同等优先权的线程或更高优先级的线程有执行的机会。 如果没有的话,那么 yield() 方法将不会起作用,并且由可执行状态后马上又被执行。
         join 方法是用于在某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执 行结束后,再继续执行当前线程。如:t.join(); // 主要用于等待 t 线程运行结束,若无此句, main 则会执行完毕,导致结果不可预测。
        notify 方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程 等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管 理的实现。
        notifyAll 会唤醒所有等待 ( 对象的 ) 线程,尽管哪一个线程将会第一个处理取决于操作系 统的实现。

5.中断线程

中断线程有很多方法:
1 )使用退出标志,使线程正常退出,也就是当 run 方法完成 后线程终止。
(2)通过 return 退出 run
(3)通过对有些状态中断抛异常退出 thread.interrupt() 中断。
(4)使用 stop 方法强行终止线程(过期)
中断线程可能出现的问题
使用 Thread.interrupt() 并不能使得线程被中断,线程还是会执行。最靠谱的方法就是 设置一个全局的标记位,然后再 Thread 中去检查这个标记位,发现标记位改变则中断线程。

6. 多线程如何避免死锁

一.什么是死锁?

       所谓死锁是指多个进 程因竞争资源而造成的一种僵局(互相等待),若无外力作用,
这些进程都将无法向前推进。
死锁产生的 4 个必要条件:
        互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一 段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程 只能等待。
        不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺 走,即只能 由获得该资源的进程自己来释放(只能是主动释放)
        请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求, 而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不 放。
        循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的 资源同时被 链中下一个进程所请求。

二.如何确保 N 个线程可以访问 N 个资源同时又不导致死锁?

       使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序, 并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加 锁和释放锁,就不会出现死锁了。
1. 加锁顺序(线程按照一定的顺序加锁)
2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,
   并释放自己占有的锁)
3. 死锁检测

7 .多线程的好处以及问题

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

8.线程通信方式

1. 同步
    同步是指多个线程通过 synchronized 关键字这种方式来实现线程间的通信。
2. wait/notify 机制

9.线程中抛出异常怎么办

       当单线程的程序发生一个未捕获的异常时我们可以采用 try....catch 进行异常的捕获,但 是在多线程环境中,线程抛出的异常是不能用 try....catch 捕获的,这样就有可能导致一 些问题的出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等。
        简单的说,如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHan dler 是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常 将造成线程中断的时候 JVM 会使用 Thread.getUncaughtExceptionHandler() 来查询线程 的 UncaughtExceptionHandler 并将线程和异常作为参数传递给 handler uncaughtEx ception()方法进行处理。

你可能感兴趣的:(Java基础合集,java,jvm,开发语言)