Java线程面试题
线程的四种启动方式
继承Thread类
在类中已经继承了某一个类的话实现Runable接口
有返回值的必须实现Callable接口
为什么要使用FutureTask来实例化Callable,看看源码:
继承了RunableFuture接口,再看看RunableFuture接口:
又继承了Runable接口,我们看看Thread类:
内部的构造方法将Runable注入进去了。
所以这儿的Callable接口会这样去实现一个多线程
使用线程池的方式
四种线程池
在Java线程池中,最顶级的接口是Executor,但是严格来说,Executor并不是一个线程池,只是一个工具人,真正的接口是ExecutorService
- newCachedThreadPool:创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用他们。适用于执行短期异步任务的程序,可以提高性能。如果没有线程可以用,那么会创建一个新的线程放到池子中,移除那些有60秒未被使用的线程。因此长时间保持空闲的线程池不会使用任何资源
- newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
- newScheduledThreadPool:可以安排在给定延迟后运行或者定期执行
- newSingleThreadExecutor:返回一个线程池,这个池子里面只有一个线程,当线程发生异常或终止后会重启一个线程来代替。
线程生命周期
新建—就绪(Runable)–运行(Running)–阻塞(Blocked)–死亡(Dead)
线程阻塞分三种:等待阻塞,同步阻塞,其他阻塞
- 等待阻塞:执行线程的wait()方法,jvm会把这个线程放到等待队列里面
- 同步阻塞:当同步锁被其他线程占用时,jvm会把该线程放到锁池中
- 其他阻塞:执行Thread.sleep()或者join()方法,或者发出了IO请求,会把这个线程设置为阻塞状态
线程死亡
- 正常结束:run()方法或者call()方法执行结束之后
- 异常结束:抛出了一个未被捕获的Exception或者Error
- 调用stop:该方法容易导致死锁,不建议使用
终止线程的四种方式
- 正常结束
- 使用退出标志
- 使用interrupt()方法
- 线程处于阻塞状态,比如使用了sleep(),wait()。当调用interrupt()方法时,会抛出一个InterruptException异常,捕获异常后通过break来跳出这个异常。
- 线程未处于阻塞状态,使用isInterrupt()判断线程的中断标志来退出循环。和使用退出标志一个道理。
- 使用stop()方法,不安全,容易导致死锁
内部方法区别
sleep和wait的区别:
sleep方法属于Thread类,而Wait方法属于Object类
调用sleep方法,线程不会释放对象锁。调用wait方法,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify方法后本线程才进入对象锁定池准备获取对象进入运行状态
sleep方法导致了程序暂停执行指定的时间,但是监控状态依赖在执行,到了指定的时间又会恢复运行状态。
start和run的区别
start方法来启动线程,实现了多线程运行。无需等待run方法体代码执行完毕就可以直接继续执行下面的代码。
run称为线程体,包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行run函数当中的代码。
Java锁
- 乐观锁
- 悲观锁
- 自旋锁
- Synchronized同步锁
CAS
全称:compare and swap/set 比较并交换,用的最多的就是在乐观锁中,总是认为自己可以完成操作