线程面试题
1、Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,他们有什么区别?
(1)sleep 是线程类(Thread)的方法,wait 是Object 类的方法。
(2)最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
(3)wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
(4)sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。
2、线程的sleep()方法和yield()方法有什么区别?
sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。两者的区别在于:
(1)【运行机会,考虑不考虑其他线程的优先级】sleep()方法会给其他线程运行的机会,不考虑其他线程的优先级,因此会给较低优先级线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。
(2)【执行相应方法后的状态】当线程执行了sleep(long millis)方法,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法,将转到就绪状态。
(3)【是否抛异常】sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常。
(4)sleep()方法比yield()方法具有更好的可移植性。
3、请说出与线程同步以及线程调度相关的方法
-wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
-sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
-notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
-notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
4、编写多线程程序有几种实现方式
(1)继承Thread类,重写 Thread 类的 run 方法。然后就是分配并启动该子类的实例
(2)实现Runnable 接口,该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
(3)Java 5以后创建线程还有第三种方式:通过线程池,实现Callable接口,该接口中的call方法可以在线程执行结束时产生一个返回值
Java 5
6、线程池
什么是线程池?【可以将线程池的作用一块回答】
线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用。
(1)减少了创建和销毁线程的次数,节省创建和销毁线程的时间和系统资源的开销。
(2)可以根据系统的承受能力,调整线程池中线程的数目,防止出现资源不足或内存消耗太多的情况。
创建线程池
通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。
Java四种线程池的使用:
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
使用线程池方式:
(1)使用线程池方式--Runnable接口
(2)使用线程池方式—Callable接口
Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。7、线程的基本状态以及状态之间的关系?
图片.png【new:新建状态;wating:等待状态;runnable:就绪状态;running:运行状态;blocked:阻塞状态;dead:死亡状态】
1.新建
用new语句创建的线程对象处于新建状态,此时它和其他java对象一样,仅被分配了内存。
2.等待
当线程在new之后,并且在调用start方法前,线程处于等待状态。
3.就绪
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态。处于这个状态的线程位于Java虚拟机的可运行池中,等待cpu的使用权。
4.运行状态
处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只会有一个线程处于这个状态。
只有处于就绪状态的线程才有机会转到运行状态。
5.阻塞状态
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才会有机会获得运行状态。
阻塞状态分为三种:
1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
2、同步阻塞:运行的线程在获取对象同步锁时,若该同步锁被别的线程占用,则JVM会把线程放入锁池中。
3、其他阻塞:运行的线程执行Sleep()方法,或者发出I/O请求时,JVM会把线程设为阻塞状态。当Sleep()状态超时、或者I/O处理完毕时,线程重新转入就绪状态。
6.死亡状态
当线程执行完run()方法中的代码,或者遇到了未捕获的异常,就会退出run()方法,此时就进入死亡状态,该线程结束生命周期。
5、synchronized关键字的用法
线程同步的方式有两种:
方式1:同步代码块
同步代码块: 在代码块声明上 加上synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
同步代码块执行原理
加了同步之后,线程进同步需要判断锁,获取锁,出同步需要释放锁,导致程序运行速度的下降
方式2:同步方法
好处:代码简洁
8、Lock接口
Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能。我们使用Lock接口,以及其中的lock()方法和unlock()方法替代同步。8、synchronized和java.util.concurrent.locks的区别
(1)synchronized和Lock接口都可以实现同步访问。
(2)synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
(3)在同步锁的释放上:
如果一个代码块或者一个方法被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁。
如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。