《面试题对标大纲》
题目列表集
1、线程的状态
2、Java 中用到的线程调度算法是什么?
3、 线程的调度策略
4、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
5、请说出与线程同步以及线程调度相关的方法。
6、sleep() 和 wait() 有什么区别?
7、你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?
8、为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
9、为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?
10、Thread 类中的 yield 方法有什么作用?
11、为什么 Thread 类的 sleep()和 yield ()方法是静态的?
12、线程的 sleep()方法和 yield()方法有什么区别?
13、如何停止一个正在运行的线程?
14、 Java 中 interrupted 和 isInterrupted 方法的区别?
15、什么是阻塞式方法?
16、Java 中你怎样唤醒一个阻塞的线程?
17、notify() 和 notifyAll() 有什么区别?
18、如何在两个线程间共享数据?
19、 Java 如何实现多线程之间的通讯和协作?
20、同步方法和同步块,哪个是更好的选择?
1、线程的状态
- 新建(new):新创建了一个线程对象。
- 就绪(可运行状态)(runnable):线程对象创建后,当调用线程对象的 start()方法,该线程处于就
绪状态,等待被线程调度选中,获取cpu的使用权。 - 运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。
注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处
于就绪状态中; - 阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时
进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。
阻塞的情况分三种:
(一). 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waitting
queue)中,使本线程进入到等待阻塞状态;
(二). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),,则JVM
会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;
(三). 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状
态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入
就绪状态。 - 死亡(dead)(结束):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束
生命周期。死亡的线程不可再次复生。
2、Java 中用到的线程调度算法是什么?
计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU 的使用权
才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用
权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机
的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。
(Java是由JVM中的线程计数器来实现线程调度)有两种调度模型:分时调度模型和抢占式调度模型。
- 分时调度模型:是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU
的时间片这个也比较好理解。 - 抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可
运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线
程会一直运行,直至它不得不放弃 CPU。
3、 线程的调度策略
线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:
(1)线程体中调用了 yield 方法让出了对 cpu 的占用权利
(2)线程体中调用了 sleep 方法使线程进入睡眠状态
(3)线程由于 IO 操作受到阻塞
(4)另外一个更高优先级线程出现
(5)在支持时间片的系统中,该线程的时间片用完
4、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
- 线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建
一个线程并启动它,它的执行便依赖于线程调度器的实现。 - 时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线
程优先级或者线程等待的时间。 - 线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你
的程序依赖于线程的优先级)。
5、请说出与线程同步以及线程调度相关的方法。
(1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理
InterruptedException 异常;
(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一
个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它
们竞争,只有获得锁的线程才能进入就绪状态;
6、sleep() 和 wait() 有什么区别?
相同:
两者都可以暂停线程的执行
- 类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
是否释放锁:sleep() 不释放锁;wait() 释放锁。 - 用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
- 用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()
或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long
timeout)超时后线程会自动苏醒。
7、你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?
- 处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没
有满足结束条件的情况下退出。 - wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满
足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的
代码:
synchronized (monitor) {
// 判断条件谓词是否得到满足
while(!locked) {
// 等待唤醒
monitor.wait();
}
// 处理其他的业务逻辑
}
8、为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
- 因为Java所有类的都继承了Object,Java想让任何对象都可以作为锁,并且 wait(),notify()等方法
用于等待对象的锁或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象
调用方法一定定义在Object类中。 - 有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线程继
承于Thread类,也不需要重新定义wait()方法的实现。然而,这样做有一个非常大的问题,一个线
程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是
不能实现,只是管理起来更加复杂。
9、为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?
当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这
个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要
调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象
锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在
同步方法或者同步块中被调用。
10、Thread 类中的 yield 方法有什么作用?
- 使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
- 当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可能是当前线程,也
可能是其他线程,看系统的分配了。
11、为什么 Thread 类的 sleep()和 yield ()方法是静态的?
Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线
程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线
程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
12、线程的 sleep()方法和 yield()方法有什么区别?
(1) sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的
机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
(2) 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态;
(3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
(4)sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议
使用yield()方法来控制并发线程的执行。
13、如何停止一个正在运行的线程?
1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。(比如循环内的状态 flag)
2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期
作废的方法。
3、使用interrupt方法中断线程。(Thread 类下)
14、 Java 中 interrupted 和 isInterrupted 方法的区别?
interrupt:用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。
注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处
理。支持线程中断的方法(也就是线程中断后会抛出interruptedException 的方法)就是在监视线程的
中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。interrupted:是静态方法,查看当前中断信号是true还是false并且清除中断信号。如果一个线程
被中断了,第一次调用 interrupted 则返回 true,第二次和后面的就返回 false 了。isInterrupted:是可以返回当前中断信号是true还是false,与interrupt最大的差别
15、什么是阻塞式方法?
阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket 的accept()方法就是
一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后
才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。
16、Java 中你怎样唤醒一个阻塞的线程?
- 首先 ,wait()、notify() 方法是针对对象的,调用任意对象的 wait()方法都将导致线程阻塞,阻塞
的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线
程,但它需要重新获取该对象的锁,直到获取成功才能往下执行; - 其次,wait、notify 方法必须在 synchronized 块或方法中被调用,并且要保证同步块或方法的锁
对象与调用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获
取某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。
17、notify() 和 notifyAll() 有什么区别?
1、如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去
竞争该对象的锁。
2、notifyAll() 会唤醒所有的线程,notify() 只会唤醒一个线程。
3、notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,
4、如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪
一个线程由虚拟机控制。
18、如何在两个线程间共享数据?
在两个线程间共享变量即可实现共享。
一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如果有对共享变量的复合操作,那么 也得保证复合操作的线程安全性。
19、 Java 如何实现多线程之间的通讯和协作?
- 可以通过中断 和 共享变量的方式实现线程间的通讯和协作
- 比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放
入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不
释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产
者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用
权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样
地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就
是线程间的协作。 - Java中线程通信协作的最常见方式:
- 一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
- 二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
- 线程间直接的数据交换:通过管道进行线程间通信:字节流、字符流
20、同步方法和同步块,哪个是更好的选择?
1、同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会
锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获
得这个对象上的锁。
2、同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以
避免死锁。
▲ 请知道一条原则:同步的范围越小越好。