今日内容
有的时候选择真的会比努力要重要!在本科的时候感觉自己的目标还是比较明确的,现在读研了,反而越来越迷茫了,你说做科研吧没有团队,你说写论文吧,写好了又没渠道发出去,,,还是继续卷Java吧~
所以我们还是继续来总结线程相关的面试题吧!
面试内容总结
- 对Lock的了解
- lock存储结构是一个int类型的状态值+双向链表,获取锁时是通过CAS来改变状态值,如果没有获取到就将该线程放入到等待链表中;释放锁时如果状态值修改成功,就会调整等待链表。
- lock锁的底层实现是AQS,在获取锁的过程中会大量使用CAS(乐观锁)+自旋。lock建议使用在低锁冲突的情况下。在JDK1.6以后,synchronized做了大量的锁优化(有偏向锁、轻量级锁)。所以在非必要的情况下还是建议使用synchronized锁做同步操作。
- synchronized底层原理
- 是由一对monitorenter/monitorexit指令实现的,monitor对象是同步的基本实现单元。JDK6提供了三种monitor的实现,也就是三种不同的锁:偏向锁、轻量级锁、重量级锁,大大改进了性能。
- 多线程中的synchronized锁升级原理
- 在锁对象的头中有一个threadid的字段,第一次访问时,threadid为空,JVM让其持有偏向锁,并且将threadid设置为该线程的id,再次进入时会先判断threadid和此时线程id是否相同,如果不同,就会把偏向锁升级会轻量级锁,通过自旋循环一定次数来获取锁并且执行一定次数,如果还没有正常的获取使用的对象,就把轻量级锁升级为重量级锁,这个过程就是锁升级!
- 锁升级目的:为了降低锁带来的性能消耗。
- synchronized和volatile的区别
- synchronized可以使用在变量、方法、代码块上;volatile仅能使用在变量上
- synchronized可以通过加锁的方式,保证原子性、可见性、有序性;volatile能保证变量在并发场景下的可见性、有序性
- synchronized可能会线程阻塞;volatile不会线程阻塞
- synchronized的性能不太好
- synchronized和ReentrantLock区别
- synchronized可以使用在类、方法、代码块上;ReentrantLock只适用于代码块锁
- synchronized底层是同步阻塞,使用的是悲观并发策略;lock是同步非阻塞,采用的是乐观并发策略
- synchronized不需要手动释放锁和开启锁;ReentrantLock必须手动获取与释放锁
- synchronized和Lock的区别?
- synchronized可以使用在类、方法、代码块上;lock只能使用在代码块上
- synchronized使用简单,如果发生异常会自动释放锁,不会造成死锁;lock需要手动加锁和释放锁,使用不当会造成死锁
- synchronized不好判断是否成功拿到锁;lock可以办到!
- 线程池的了解
- newCacheThreadPool:用来处理大量短时间工作任务的线程池。它会试图缓存线程并重用,如果没有缓存线程可以用,就会创建新的工作线程,如果线程闲置时间超过60s,就会被终止并且移出缓存!
- newFixedThreadPool:用来创建一个可重用且固定线程数的线程池,以共享的无界队列方式来运行这些线程!也就是如果任务数量超过了活动队列数目时,将会再工作队列中等待空闲的线程出现,如果有工作线程退出了,将会有新的工作线程被创建。
- newScheduleThreadPool:可以进行定时或者周期性的工作调度!
- newSingleThreadExcutor:工作线程数目被限制为1,该线程池可以在线程死后,重新启动一个线程来替换之前的线程,从而可以避免其改变线程数目~
- ThreadPoolExecutor:最原始的线程池创建!
- 为什么要用线程池
- 可以控制最大并发数
- 完成线程复用
- 管理线程
- 线程池中的七个参数
- corePoolSize:线程池中的线程数
- maximumPoolSize:线程池中的最大线程数
- keepAliveTime:多少时间内会被销毁
- unit:keepAliveTime的单位
- workQueue:任务队列,提交但未被执行的任务
- threadFactory:线程工厂,创建线程,一般默认
- handler:拒绝策略,消息太多来不及处理,如何拒绝任务
- 用sleep时如何做唤醒?
- 可以调用interrupt()方法,会抛出InterruptException异常,会使线程提前结束TIMED-WATING状态!
- ThreadLocal是什么?有哪些使用场景?
- ThreadLocal是线程本地变量,它会为每个使用该变量的线程提供独立的变量副本,每一个线程都可以独立的改变自己的副本,而不会影响起到线程所对应的副本
- 使用场景:数据库连接、session管理
- 如何保证线程安全?
- 使用安全类,比如java.util.concurren下的类
- 使用自动锁synchronized
- 使用手动锁Lock
- 如何防止死锁
- 尽量使用tryLock,设置超时时间,超时可退出,可以防止死锁
- 尽量使用java.util.concurrent代替手写锁
- 尽量降低锁的使用颗粒,不要多个功能用同一把锁!
- 尽量减少同步代码块的使用
- 扩展知识
- 死锁:简单来讲就是:线程双方拿着对方想要的锁,从而发生阻塞线程,也就是死锁线程。就像线程A有独占锁a并且要去尝试拿独占锁b,与此同时,线程B有独占锁b也要去拿独占锁a!
- AQS:抽象队列同步器,将暂时获取不到锁的线程放入队列之中,它是基于CLH队列锁(自旋锁)实现的,将每个请求共享资源的线程封装成一个CLH锁队列的Node,从而实现锁的分配
- 锁:见下一篇~
阶段性总结
线程这一块的知识点比较多,里面还会涉及很多底层原理和算法,有兴趣的同学可以继续深研。线程这部分在平时写代码做项目的时候可能接触的很少,但是里面的很多机制还是很值得学习的,特别是在后期做并发操作的时候,如果你懂底层原理,那么后面在做并发就不会显得那么吃力了~
学无止境,所以大家也一定要加油奥~