- 说说Thread类的常用方法
- currentThread():返回当前正在执行的线程;
- interrupted():返回当前执行的线程是否已经被中断;
- sleep(long millis):使当前执行的线程睡眠多少毫秒数;
- yield():使当前执行的线程自愿暂时放弃对处理器的使用权并允许其他线程执行;
- run()和start()有什么区别?
- run()方法被称为线程执行体,它的方法体代表了线程需要完成的任务,而start()方法用来启动线程。
- 线程是否可以重复启动,会有什么后果?
- 只能对处于新建状态的线程调用start()方法,否则将引发IllegalThreadStateException异常。
- 介绍一下线程的生命周期
- 新建(New)、就绪(Ready)、运行(Running)、阻塞(Blocked)和死亡(Dead)
- 如何实现线程同步?
- 同步方法
- 同步代码块
- ReentrantLock
- volatile
- 原子变量
- 说一说Java多线程之间的通信方式
- wait()、notify()、notifyAll()
- await()、signal()、signalAll()
- BlockingQueue
- wait和notify
- wait()方法可以让当前线程释放对象锁并进入阻塞状态。notify()方法用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列
- 如何实现子线程先执行,主线程再执行?
- 启动子线程后,立即调用该线程的join()方法,则主线程必须等待子线程执行完成后再执行。
- 说一说synchronized与Lock的区别(面试真题)
- synchronized是Java关键字,在JVM层面实现加锁和解锁;Lock是一个接口,在代码层面实现加锁和解锁。
- synchronized可以用在代码块上、方法上;Lock只能写在代码里。
- synchronized在代码执行完或出现异常时自动释放锁;Lock不会自动释放锁,需要在finally中显示释放锁。
- synchronized会导致线程拿不到锁一直等待;Lock可以设置获取锁失败的超时时间。
- synchronized无法得知是否获取锁成功;Lock则可以通过tryLock得知加锁是否成功。
- synchronized锁可重入、不可中断、非公平;Lock锁可重入、可中断、可公平/不公平,并可以细分读写锁以提高效率。
- synchronized可以修饰静态方法和静态代码块吗?
- synchronized可以修饰静态方法,但不能修饰静态代码块。
- 如果不使用synchronized和Lock,如何保证线程安全?
- volatile
- 原子变量
- 本地存储
- 不可变的
- 说一说Java中乐观锁和悲观锁的区别
- 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。Java中悲观锁是通过synchronized关键字或Lock接口来实现的。
- 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据
- 了解Java中的锁升级吗?
- 无锁
- 偏向锁
- 轻量级锁
- 重量级锁
- 分段锁是怎么实现的?
- 容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率
- volatile关键字有什么用?
- 保证可见性
- 禁止指令重排
- 介绍下ThreadLocal和它的应用场景
- 每个线程都有自己专属的存储容器,它用来存储线程私有变量。内部真正存取是一个Map
- 经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。保证每个线程的都在各自的 Connection 上进行数据库的操作
- ThreadLocal还经常用于管理Session会话,将Session保存在ThreadLocal中,使线程处理多次处理会话时始终是同一个Session。
- 请介绍ThreadLocal的实现原理,它是怎么处理hash冲突的?
- 主要利用ThreadLocalMap类,它实现类似map的功能,每一个元素是一个Entry;每个线程有一个自己的map
- get和set操作时都要获取当前线程,取出当前线程的ThreadLocalMap,然后实施相应的get、set操作
- 当出现Hash冲突时采用线性查找的方式,如果位置上已经有其他key值的元素利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置
- 介绍一下线程池
- 线程池在系统启动时即创建大量空闲的线程,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法。
- 介绍一下线程池的工作流程
- 判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。
- 判断任务队列是否已满,没满则将新提交的任务添加在工作队列。
- 判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行饱和(拒绝)策略。
- 线程池都有哪些状态
- Runnning:能接受新提交的任务,能处理阻塞队列中的任务。
- Shutdown:不再接受新提交的任务但可以继续处理阻塞队列中已保存的任务
- Stop:不能接受新任务,也不处理队列中的任务
- Tidying:所有的任务都已终止
- Terminated:在terminated() 方法执行完后进入该状态
- 谈谈线程池的拒绝策略
- 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略:
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- DiscardPolicy:也是丢弃任务,但是不抛出异常。
- DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复该过程)。
- CallerRunsPolicy:由调用线程处理该任务。
- 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略:
- 线程池的队列大小你通常怎么设置?
- CPU密集型任务
- 尽量使用较小的线程池,一般为CPU核心数+1
- IO密集型任务
- 可以使用稍大的线程池,一般为2*CPU核心数
- 混合型任务
- 分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理
- CPU密集型任务
- 线程池有哪些参数,各个参数的作用是什么?
- corePoolSize 核心工作线程数
- maximumPoolSize 最大线程数
- keepAliveTime 多余线程存活时间
- workQueue 队列
- threadFactory 线程创建工厂
- handler 拒绝策略