分享50道Java多线程高频面试题,面试不用愁

本文转载自:分享50道Java多线程高频面试题,面试不用愁


1. 什么是进程?

是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。

2. 什么是线程?

线程是操作系统能够进行运算调度的最小单位。

它被包含在进程之中,是进程中的实际运作单位。

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

3. 进程和线程的区别?

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。

线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

4. 多线程和单线程有什么区别?

单线程程序:程序执行过程中只有一个有效操作的序列,不同操作之间都有明确的执行先后顺序,容易出现代码阻塞

多线程程序:有多个线程,线程间独立运行,能有效地避免代码阻塞,并且提高程序的运行性能

5. 为什么要使用多线程?

  1. 使用多线程可以减少程序的响应时间。 在单线程的情况下,如果某个程序很耗时或者陷入长时间等待(如等待网络响应),此时程序将不会相应鼠标和键盘等操作,使用多线程后,可以把这个耗时的线程分配到一个单独的线程去执行,从而是程序具备了更好的交互性。
  2. 与进程相比,线程的创建和切换开销更小。 由于启动一个新的线程必须给这个线程分配独立的地址空间,建立许多数据结构来维护线程代码段、数据段等信息,而运行于同一个进程内的线程共享代码段、数据段,线程的启动或切换的开销就比进程要少很多。同时多线程在数据共享方面效率非常高。
  3. 多CPU或多核心计算机本身就具有执行多线程的能力。 如果使用单个线程,将无法重复利用计算机资源,造成资源的巨大浪费。因此在多CPU计算机上使用多线程能提高CPU的利用率。
  4. 使用多线程能简化程序的结构,使用程序便于理解和维护。 一个非常复杂的进程可以分成多个线程来执行。

6. 什么是线程安全?

当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获取正确的结果,那这个对象是线程安全的。 ——<<深入Java虚拟机>>

7. 为何要使用线程同步?

Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突。

因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

8. 如何确保线程安全?

  • 对非安全的代码进行加锁控制
  • 使用线程安全的类
  • 多线程并发情况下,线程共享的变量改为方法级的局部变量

9. 什么是原子操作?

检查数值、改变数值,以及可能发生的睡眠操作均作为单一的、不可分割的原子操作完成。

10. Java内存模型是什么?

1、Java中所有变量都储存在主存中,对于所有线程都是共享的(因为在同一进程中),每个线程都有自己的工作内存或本地内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,而线程之间无法相互直接访问,变量传递均需要通过主存完成,但是在程序内部可以互相调用(通过对象方法),所有线程间的通信相对简单,速度也很快。

2、进程间的内部数据和状态都是相互完全独立的,因此进程间通信大多数情况是必须通过网络实现。线程本身的数据,通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。

3、CPU对于各个线程的调度是随机的(分时调度),在Java程序中,JVM负责线程的调度。 线程调度是指按照特定的机制为多个线程分配CPU的使用权,也就是实际执行的时候是线程,因此CPU调度的最小单位是线程,而资源分配的最小单位是进程。

11. Java中堆和栈有什么不同?

栈:在函数中定义的基本类型的变量和对象的引用变量都是在函数的栈内存中分配。

堆:堆内存用于存放由new创建的对象和数组。

从通俗化的角度来说,堆是用来存放对象的,栈是用来存放执行程序的

12. JVM中哪个参数是用来控制线程的栈堆栈小的

-Xss参数用来控制线程的堆栈大小。

13. 什么是竞态条件?你怎样发现和解决竞争?

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。

在临界区中使用适当的同步就可以避免竞态条件。

界区实现方法有两种,一种是用synchronized,一种是用Lock显式锁实现。

14. 线程安全的级别

不可变

不可变的对象一定是线程安全的,并且永远也不需要额外的同步。

Java类库中大多数基本数值类如Integer、String和BigInteger都是不可变的。

无条件的线程安全

由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。

如 Random 、ConcurrentHashMap、Concurrent集合、atomic

有条件的线程安全

有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。

有条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器

非线程安全(线程兼容)

线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。

如ArrayList HashMap

线程对立

线程对立是那些不管是否采用了同步措施,都不能在多线程环境中并发使用的代码。

如如System.setOut()、System.runFinalizersOnExit()

15. 你对线程优先级的理解是什么?

每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OSdependent)。

可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10),1代表最低优先级,10代表最高优先级。

16. 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?

线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦创建一个线程并启动它,它的执行便依赖于线程调度器的实现。

时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。

线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择。

17. 在多线程中,什么是上下文切换(context-switching)?

单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。

操作系统中,CPU时间分片切换到另一个就绪的线程,则需要保存当前线程的运行的位置,同时需要加载需要恢复线程的环境信息。

18. 用户线程和守护线程有什么区别?

守护线程都是为JVM中所有非守护线程的运行提供便利服务: 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。

User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。

因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

19. 如何创建守护线程?以及在什么场合来使用它?

任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bool on);true则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常。

守护线程相当于后台管理者 比如 : 进行内存回收,垃圾清理等工作

20. 线程的状态转换?

  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()方法,该线程结束生命周期。

21. 线程中断是否能直接调用stop,为什么?

Java提供的终止方法只有一个stop,但是不建议使用此方法,因为它有以下三个问题:

  • stop方法是过时的 从Java编码规则来说,已经过时的方式不建议采用.
  • stop方法会导致代码逻辑不完整 stop方法是一种"恶意"的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的.

22. 线程的sleep()方法和yield()方法有什么区别?

  1. sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
  2. 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
  3. sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
  4. sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

23. 请说出与线程同步以及线程调度相关的方法。

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
  • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

24. 举例说明同步和异步。

如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。

25. 不使用stop停止线程?

当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

使用自定义的标志位决定线程的执行情况

public class SafeStopThread implements Runnable{  
   private volatile boolean stop=false;//此变量必须加上volatile  
   int a=0;  
   @Override  
    public void run() {  
        // TODO Auto-generated method stub  
        while(!stop){  
               synchronized ("") {  
                    a++;  
                    try {  
                        Thread.sleep(100);  
                    } catch (Exception e) {  
                        // TODO: handle exception  
                    }  
                    a--;  
                    String tn=Thread.currentThread().getName();  
                    System.out.println(tn+":a="+a);  
                }  
        }  
      //线程终止  
     public void terminate(){  
         stop=true;  
      }  
  public static void main(String[] args) {  
       SafeStopThread t=new SafeStopThread();  
       Thread t1=new Thread(t);  
       t1.start();  
       for(int i=0;i<5;i++){   
           new Thread(t).start();  
       }  
     t.terminate();  
   }  
} 

26. Java中如何实现线程?各有什么优缺点,比较常用的是那种,为什么?

在语言层面有两种方式。java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口所以你可以继承java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程。

Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好了。

27. 如何控制某个方法允许并发访问线程的大小?

Semaphore两个重要的方法就是semaphore.acquire() 请求一个信号量,这时候的信号量个数-1(一旦没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信号量)semaphore.release()释放一个信号量,此时信号量个数+1

public class SemaphoreTest {  
    private Semaphore mSemaphore = new Semaphore(5);  
    public void run(){  
        for(int i=0; i< 100; i++){  
            new Thread(new Runnable() {  
                @Override  
                public void run() {  
                    test();  
                }  
            }).start();  
        }  
    }  
  
    private void test(){  
        try {  
            mSemaphore.acquire();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println(Thread.currentThread().getName() + " 进来了");  
        try {  
            Thread.sleep(1000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println(Thread.currentThread().getName() + " 出去了");  
        mSemaphore.release();  
    }  
}  

28. 在Java中什么是线程调度?

线程调度是指系统为线程分配处理器使用权的过程。 主要调度方式有两种,分别是协同式线程调度和抢占式线程调度。

协同式线程调度:线程的执行时间由线程本身控制,当线程把自己的工作执行完了之后,主动通知系统切换到另一个线程上。

好处是切换操作对于线程自己是可知的,没什么线程同步问题。
坏处是线程执行时间不可控,可能会一直阻塞然后系统崩溃。
抢占式线程调度:每个线程由系统分配执行时间,不由线程本身决定。线程的执行时间是系统可控的,不会有一直阻塞的问题。

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

抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

30. 线程类的构造方法、静态块是被哪个线程调用的?

线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。

31. 在实现Runnable的接口中怎么样访问当前线程对象,比如拿到当前线程的名字?

Thread t = Thread.currentThread();
String name = t.getName();
System.out.println("name=" + name);

32. 什么是线程池?为什么要使用它?为什么使用Executor框架比使用应用创建和管理线程好?

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。

为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。

Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

33. 常用的线程池模式以及不同线程池的使用场景?

以下是Java自带的几种线程池:

1、newFixedThreadPool 创建一个指定工作线程数量的线程池。 每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

2、newCachedThreadPool 创建一个可缓存的线程池。 这种类型的线程池特点是:

  • 工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE),这样可灵活的往线程池中添加线程。
  • 如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

3、newSingleThreadExecutor创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。

单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

4、newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

34. 在Java中Executor、ExecutorService、Executors的区别?

Executor 和 ExecutorService 这两个接口主要的区别是:

  • ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口
  • Executor 和 ExecutorService 第二个区别是:Executor 接口定义了 execute()方法用来接收一个Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable接口的对象。
  • Executor 和 ExecutorService 接口第三个区别是 Executor 中的 execute() 方法不返回任何结果,而 ExecutorService 中的 submit()方法可以通过一个 Future 对象返回运算结果。
  • Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。

Executors 类提供工厂方法用来创建不同类型的线程池。

比如: newSingleThreadExecutor() 创建一个只有一个线程的线程池,newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池,newCachedThreadPool()可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。

35. 如何创建一个Java线程池?

Java通过Executors提供四种线程池,分别为:

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

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

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

36. Thread 类中的start() 和 run() 方法有什么区别?

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。

当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

37. Java线程池中submit() 和 execute()方法有什么区别?

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

38. Java中notify 和 notifyAll有什么区别?

notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。

40. 为什么wait, notify 和 notifyAll这些方法不在thread类里面?

一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。

如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。

简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

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

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

最主要的原因是为了防止以下这种情况

// 等待者(Thread1)
while (condition != true) { // step.1
    lock.wait() // step.4
}

// 唤醒者(Thread2)
condition = true; // step.2
lock.notify(); // step.3

在对之前的代码去掉 synchronized 块之后,如果在等待者判断 condition != true 之后而调用 wait() 之前,唤醒者**将 condition 修改成了 true 同时调用了 notify()**的话,那么等待者在调用了 wait() 之后就没有机会被唤醒了。

42. 讲下join,yield方法的作用,以及什么场合用它们?

join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。

yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。

42. sleep方法有什么作用,一般用来做什么?

sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复。注意这里的恢复并不是恢复到执行的状态,而是恢复到可运行状态中等待CPU的宠幸。

43. Java多线程中调用wait() 和 sleep()方法有什么不同?

Java程序中wait和sleep都会造成某种形式的暂停,它们可以满足不同的需要。

wait存在于Object类中;sleep存在于Thread类中。
wait会让出CPU资源以及释放锁;sleep只会释放CPU资源。
wait只能在同步块中使用;sleep没这限制。
wait需要notify(或 notifyAll)唤醒,进入等锁状态;sleep到指定时间便会自动恢复到运行状态。

44. 为什么Thread里面的大部分方法都是final的?

不能被重写,线程的很多方法都是由系统调用的,不能通过子类覆写去改变他们的行为。

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

Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。

该代码只有在某个A线程执行时会被执行,这种情况下通知某个B线程yield是无意义的(因为B线程本来就没在执行)。因此只有当前线程执行yield才是有意义的。通过使该方法为static,你将不会浪费时间尝试yield 其他线程。

46. 什么是阻塞式方法?

阻塞式方法是指程序会一直等待该方法完成期间不做其他事情。

ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。

此外,还有异步和非阻塞式方法在任务完成前就返回。

47. 如何强制启动一个线程?

在Java里面没有办法强制启动一个线程,它是被线程调度器控制着

48. 一个线程运行时发生异常会怎样?

简单的说,如果异常没有被捕获该线程将会停止执行。

Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。

当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。

49. 在线程中你怎么处理不可控制异常?

在Java中有两种异常。

非运行时异常(Checked Exception):这种异常必须在方法声明的throws语句指定,或者在方法体内捕获。例如:IOException和ClassNotFoundException。

运行时异常(Unchecked Exception):这种异常不必在方法声明中指定,也不需要在方法体中捕获。例如,NumberFormatException。

因为run()方法不支持throws语句,所以当线程对象的run()方法抛出非运行异常时,我们必须捕获并且处理它们。当运行时异常从run()方法中抛出时,默认行为是在控制台输出堆栈记录并且退出程序。

好在,java提供给我们一种在线程对象里捕获和处理运行时异常的一种机制。实现用来处理运行时异常的类,这个类实现UncaughtExceptionHandler接口并且实现这个接口的uncaughtException()方法。示例:

package concurrency;

import java.lang.Thread.UncaughtExceptionHandler;

public class Main2 {
    public static void main(String[] args) {
        Task task = new Task();
        Thread thread = new Thread(task);
        thread.setUncaughtExceptionHandler(new ExceptionHandler());
        thread.start();
    }
}

class Task implements Runnable{
    @Override
    public void run() {
        int numero = Integer.parseInt("TTT");
    }
}

class ExceptionHandler implements UncaughtExceptionHandler{
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.printf("An exception has been captured\n");
        System.out.printf("Thread:  %s\n", t.getId());
        System.out.printf("Exception:  %s:  %s\n", e.getClass().getName(),e.getMessage());
        System.out.printf("Stack Trace:  \n");
        e.printStackTrace(System.out);
        System.out.printf("Thread status:  %s\n",t.getState());
    }
}

当一个线程抛出了异常并且没有被捕获时(这种情况只可能是运行时异常),JVM检查这个线程是否被预置了未捕获异常处理器。如果找到,JVM将调用线程对象的这个方法,并将线程对象和异常作为传入参数。

Thread类还有另一个方法可以处理未捕获到的异常,即静态方法setDefaultUncaughtExceptionHandler()。这个方法在应用程序中为所有的线程对象创建了一个异常处理器。

当线程抛出一个未捕获到的异常时,JVM将为异常寻找以下三种可能的处理器。

  • 首先,它查找线程对象的未捕获异常处理器。
  • 如果找不到,JVM继续查找线程对象所在的线程组(ThreadGroup)的未捕获异常处理器。
  • 如果还是找不到,如同本节所讲的,JVM将继续查找默认的未捕获异常处理器。
  • 如果没有一个处理器存在,JVM则将堆栈异常记录打印到控制台,并退出程序。

50. 为什么你应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。

  1. 一般来说,wait肯定是在某个条件调用的,不是if就是while
  2. 放在while里面,是防止出于waiting的对象被别的原因调用了唤醒方法,但是while里面的条件并没有满足(也可能当时满足了,但是由于别的线程操作后,又不满足了),就需要再次调用wait将其挂起。
  3. 其实还有一点,就是while最好也被同步,这样不会导致错失信号。
while(condition){
    wait();
}

本文转载自:分享50道Java多线程高频面试题,面试不用愁

发布了209 篇原创文章 · 获赞 13 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Java_supermanNO1/article/details/103734646