面试题一:线程中的start和run方法有什么区别
Java中线程是通过Thread类来实现的,每个线程都是通过特定的Thread对象所对应的run方法来完成
- start()方法来启动线程,真正的实现多线程,这时无需等待run()方法体代码执行完成,可以直接继续执行下面的代码,通过Thread类的start()方法来实现一个线程,这是此线程是处于就绪状态的,并没有运行,然后通过Thread类调用run()方法来完成运行操作
- run() 方法当作普通方法的方式调用。程序还是要顺序执行,要等待 run 方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
而start和run的区别可以比作汽车的钥匙和引擎,如果你没有钥匙的话就不会启动汽车
想要分清他们的区别还是要看代码更加直观:
可以看到run并没有创建线程,start创建了一个线程
面试题二:线程是如何通讯的?它的通讯方法有哪些?(说出你知道的所有通讯方法)
首先线程间的通信是指等多个线程之间操作同一份数据的时候,互相告知对方自己的状态,避免对同一变量进行争夺
在 Java 中,线程通讯的实现方法主要有以下几种:
- Object 类下的 wait()、notify() 和 notifyAll() 方法。
- Condition 类下的 await()、signal() 和 signalAll() 方法。
- LockSupport 类下的 park() 和 unpark() 方法。
Object 类下的 wait()、notify() 和 notifyAll() 方法:
wait():会让当前线程进入等待状态,并且释放所持有的锁
notify():随即唤醒一个等待该锁的其他线程,如果有多个线程都在等待这个对象的锁,这个方法只会唤醒其中一个线程。
notifyAll():这个方法会唤醒所有正在等待这个对象的锁的线程。
Condition 类下的 await()、signal() 和 signalAll() 方法:
分别对应上述的wait、notify、notifyAll
LockSupport 类下的 park() 和 unpark() 方法:
park():这方法会让当前线程进入等待,如果调用unpark()方法或者被线程终端,那么这个线程就i可以从park()方法返回
unpark(Thread thread):这个方法会让执行线程从park()方法返回
面试题三:说一下线程的生命周期
线程的生命周期主要分为五个阶段,NEW、RUNNABLE、BLOCAKED、WAITING、TIMED_WAITING、TERMINATED
- NEW(新建状态):new Thread() 时线程的状态。
- RUNNABLE(可运行/运行状态):调用 start() 方法后的状态。
- BLOCKED(阻塞状态):调用了 synchronized 加锁之后的状态。获得锁之后就从 BLOCKED 状态变成了 RUNNABLE 状态。
- WAITING(无时限等待状态):调用了 wait() 方法之后会进入此状态。
- TIMED_WAITING(有时限等待状态):调用了 sleep(long millis) 方法之后会进入此状态。
- TERMINATED(终止状态):线程任务执行完成之后就变成此状态。
面试题四:如何停止线程
自定义标识符:如定义一个flag变量true继续false退出
使用interrupt()方法:interrupt方法可以用来中断线程,但是并不会停止线程。它只是给线程设置一个中断标志。线程需要检查这个标志,然后决定是否停止执行。
使用stop()方法(已经放弃使用了不安全)
面试题五:wait()方法和sleep()方法有什么区别?
Wait和sleep都是可以用来暂停当前线程的执行,但是他们还是有一定区别:
- 所属类:wait是Object类中的,sleep是Thread类中的
- 锁处理:当线程执行wait时,会释放它当前持有的对象锁,进入等待状态。而执行sleep的时候不会释放锁
- 使用方式:wait必须在同步块或者同步方法(即synchronized)中调用,sleep任何地方都可以调用
- 使用场景:wait主要用于线程间通信,sleep用于暂停执行
面试题六:线程池相比于线程有什么优点?
线程池是一种管理和复用线程的机制,他预先创建一组线程,并且维护一个任务队列,当任务来的时候,会从线程池中选择线程去执行任务,而不是直接去创建一个线程。
线程池是一种管理和复用线程的机制,优点有以下几种:
- 线程重用(减低资源消耗):线程池可以重复利用已创建的线程,可以降低线程创建和销毁造成的消耗
- 提高响应速度:当任务到达的时候,如果线程池中有空闲的线程,那么这个任务可以立即得到执行,而不需要等待线程的创建
- 提供更强大的功能:线程池具备可拓展性,允许开大人员向其增加更多的功能。例如线程池的任务队列,使用任务队列可以存储更多的待执行的任务
- 提高系统的稳定性:线程池可以限制并发线程的数量,避免系统因为线程过多而导致资源耗尽或系统奔溃
面试题七:说下线程池创建参数都有哪些?它们都有哪些含义?
在Java中线程池是由ThreadPoolexecutor类实现的,其有多个参数:
- corePoolSize(核心线程数):这是线程池中能够同时执行线程的数量。即使线程处于空闲状态,核心线程也不会销毁
- maximumPoolSize(最大线程数):当任务队列已满且核心线程数以达到上限时,线程池会创建新的线程,直到达到最大线程数。
- keepAliveTime(空闲线程最大存活时间):当线程池中的线程数量大于核心线程且处于空闲的状态,那么在指定时间后,这个空闲线程就会被销毁
- unit(空闲线程存活时间单位):通常TimeUnit.SECONDS秒级。
- workQueue(工作队列):新任务提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。JDK提供四种任务队列:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue和PriorityBlockingQueue
- ArrayBlockingQueue:基于数组的有界阻塞队列,按照FIFO(先进先出)排序,新任务进来后会放到队尾,有界的数组可以防止资源耗尽问题。达能线程池中的线程数量达到了corePoolSize后再有新任务到来,则会将任务梵高该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
- LinkedBlockingQueue:基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
- SynchronousQueue:一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略
- PriorityBlockingQueue:具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
- threadFactory(线程工厂):可以用来设定线程名,是否为daemon(守护线程)线程
- handler(拒绝策略):这是当工作队列达到了最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交过来,该如何处理。JDK提供了四种拒绝策略CallerRunsPolicy、AbortPolicy、DiscarPolicy和DiscardOldestPolicy
- CallerRunsPolicy:在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务
- AbortPolicy:直接丢弃任务,抛出RejectedExecutionException异常
- DiscarPolicy:直接抛弃任务,什么也不做
- DiscarPolicy:抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
面试题八:线程工厂有什么用?不设置线程工厂会怎样?
在Java中线程工厂是一个接口,主要用于创建新的线程,它通常与线程池一起使用,主要用于控制创建新线程时的一些行为,比如设置线程的优先级、名称等
如果在创建线程池的时候没有设置线程工厂,那么线程池会使用默认的线程工厂,默认的线程工厂会创建一个线程,为欸其设置线程池编号和线程编号组成的名称,设置它的优先级为正常优先级,且不是守护线程
面试题九:线程的优先级有什么用?如何设置线程池的优先级?
线程的优先级用证书表示,范围是1-10,数字越大优先级越高,线程的默认优先级为5
线程的优先级越高,表示它在竞争 CPU 资源时更有可能被调度执行。然而,线程优先级的具体行为在不同的操作系统和 Java 虚拟机实现中可能会有所不同。所以,线程优先级仅仅是给操作系统一个提示,告诉它应该优先调度哪个线程,但操作系统可能不会严格按照优先级来调度线程。
Java 中,线程的优先级由 Thread 类的 setPriority() 和 getPriority() 方法来设置和获取线程的优先级。
面试题十:说一下线程池的执行流程
线程池执行流程主要包括以下几个步骤:
- 提交任务:当提交一个任务到线程池的时候,线程池首先会判断核心线程池的线程是都都在执行任务,如果没有,则会创建一个新的工作来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程
- 等待队列:线程池会判断等待队列是否已经满,如果没有满,则添加任务到等待队列中,如果满了,则进入下一个流程
- 非核心线程:线程池会尝试创建一个非核心的线程来执行任务,如果创建失败(达到最大线程数),则会拒绝任务
- 执行任务:线程池中的线程(无论是核心线程还是非核心线程)会执行任务,执行任务可能是新提交的任务,也可能是等待队列中的任务