「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
本期是【大厂面试】系列文章的第2期
面试现场
面试官:看你简历上写了精通多线程,来聊聊,为什么要使用多线程呢?
独白:精通...您不会真信了吧...
大彬:使用多线程最主要的原因是提高系统的资源利用率。
大彬:多个线程同时运行,可以减少线程上下文切换的开销,提高并发的能力和CPU的利用效率。
大彬:在平时工作中多线程也是常见的。比如Tomcat每处理一个请求都会从线程连接池里取一个线程去处理。
面试官:嗯,那什么场景可以使用多线程呢?
大彬:一些并发量大的场景,比如读入大量文件写入数据库,使用多线程能够极大提高效率。
大彬:耗时比较长的任务,比如用户注册之后发送邮件、短信之类的操作,这种任务就算失败了也不是特别重要,可以使用异步线程去处理。
大彬:定时任务,比如定期更新配置文件、备份数据之类的任务。
面试官:嗯,平时在使用多线程的时候,可能会遇到线程安全的问题吧。讲讲什么是线程安全?
大彬:我是这么理解的,当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
面试官:那你平时怎么处理线程安全问题的?
独白:幸亏看了【面试笔记】,不慌哈哈
大彬:这个还得具体问题具体分析。首先判断有没有线程安全问题,若有则根据具体的情况去处理线程安全的问题。
大彬:比如涉及到操作的原子性,可以考虑使用原子类。
大彬:如果涉及到对线程的控制,可以考虑线程工具类CountDownLatch
/Semaphore
等等。
大彬:集合类的话,考虑java.util.concurrent
包下的集合类。
大彬:还有synchronized
和lock
包下的类,redis分布式锁等。
面试官:刚刚提到synchronized
,讲讲它的底层原理?
独白:底层原理...裂开
大彬:synchronized
同步代码块的实现是通过 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor 的持有权。
独白:monitor对象存在于每个Java对象的对象头中,synchronized
锁便是通过这种方式获取锁的,这也是为什么Java中任意对象可以作为锁的原因。
大彬:其内部包含一个计数器,当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在 执行 monitorexit 指令后,将锁计数器设为0 ,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
大彬:synchronized
修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED
访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
面试官:那synchronized和ReentrantLock有什么区别呢?
- 使用synchronized关键字实现同步,线程执行完同步代码块会自动释放锁,而ReentrantLock需要手动释放锁。
- synchronized是非公平锁,ReentrantLock默认是非公平锁,可以设置为公平锁。
- ReentrantLock上等待获取锁的线程是可中断的,线程可以放弃等待锁。而synchonized会无限期等待下去。
- ReentrantLock 可以设置超时获取锁。在指定的截止时间之前获取锁,如果截止时间到了还没有获取到锁,则返回。
- ReentrantLock 的 tryLock() 方法可以尝试非阻塞的获取锁,调用该方法后立刻返回,如果能够获取则返回true,否则返回false。
面试官:什么叫公平锁和非公平锁?
大彬:如果是按照线程访问顺序去获取对象锁,则为公平锁,否则为非公平锁。
面试官:知道什么是CAS吗?
大彬:嗯,CAS全称Compare And Swap,比较与交换,是乐观锁的主要实现方式。CAS 在不使用锁的情况下实现多线程之间的变量同步。ReentrantLock 内部的 AQS 和原子类内部都使用了 CAS。
大彬:CAS算法涉及到三个操作数:需要读写的内存值 V;进行比较的值 A;要写入的新值 B。
大彬:只有当 V 的值等于 A 时,才会使用原子方式用新值B来更新V的值,否则会继续重试直到成功更新值。
大彬:以 AtomicInteger 为例,AtomicInteger 的 getAndIncrement()方法底层就是CAS实现,关键代码是 compareAndSwapInt(obj, offset, expect, update)
,其含义就是,如果obj
内的value
和expect
相等,就证明没有其他线程改变过这个变量,那么就更新它为update
,如果不相等,那就会继续重试直到成功更新值。
面试官:基础掌握的不错,今天面试就到这吧~