多线程——面试中常考的内容(11)
从这次开始多线程的学习进入了一个由工作、面试常用转变为仅面试要考的部分更侧重一些八股文的性质说白了就是背虽然对我这种文科生来说无所谓......希望大家对这些知识能有所了解因为这些考到的频率仍然很高。常见的锁策略如果你自己需要实现一把锁你认为标准库给你提供的锁不够用你需要关注锁策略。其实synchronized已经非常好用了足以覆盖绝大多数的使用场景。1.悲观锁vs乐观锁这不是针对某一种具体的锁而是某个锁具有“悲观”或者“乐观”特性。1悲观锁加锁的时候预测接下来的锁竞争情况非常激烈就需要针对这样的情况额外做一些工作比如有一把锁有二十个线程尝试获取锁每个线程加锁的频率都很高一个线程加锁的时候很可能被另一个线程占用着。2乐观锁加锁的时候预测接下来的锁竞争的情况不激烈就不需要做额外工作有一把锁假设只有两个线程尝试获取这把锁每个线程加锁的频率都很低一个线程加锁的时候大概率另一个线程没有和他竞争。2.重量级锁vs轻量级锁重量级锁在悲观的场景下此时就要付出更多的代价更低效轻量级锁在乐观的场景下此时付出的代价会更小更高效3.挂起等待锁vs自旋锁挂起等待锁是重量级锁的典型实现操作系统内核级别的加锁的时候发现竞争就会使该线程进入阻塞状态后续就需要内核进行唤醒了。自旋锁是轻量级锁的典型实现是应用程序级别的加锁的时候发现竞争一般也不是进入阻塞而是通过忙等的方式来进行等待。因此对应关系为悲观锁——重量级锁——挂起等待锁乐观锁——轻量级锁——自旋锁synchronized在前三种锁策略当中充当的角色并不是其中的任何一方而是自适应的JVM内部会统计每个锁竞争的激烈程度如果竞争不激烈就对应乐观锁一方反之则对应悲观锁一方。4.普通互斥锁vs读写锁普通互斥锁就是synchronized加锁与解锁读写锁就是加锁中分为读方式加锁与写方式加锁还有解锁一共三种。多个线程读取一个数据本身就是数据安全的但多个线程读取时即使有一个线程修改也会涉及到线程安全问题。读写锁适用于读多写少的情况大部分操作在读少数操作在写如果你把读和写加上普通的互斥锁意味着锁冲突非常严重而读写锁确保读锁与读锁之间不是互斥的不会产生阻塞。也就是说读锁与写锁之间才会产生互斥写锁与写锁之间也会产生互斥。这样在保证线程安全的前提下降低锁冲突的概率提高效率。当然synchronized不是读写锁。5.可重入锁vs不可重入锁可重入这个概念我们之前已经提过了就是一个线程一把锁连续加锁多次如果不构成死锁就是可重入的。synchronized是可重入锁。6.公平锁vs非公平锁公平锁就是要自己设定一个公平的条件非公平锁之下锁默认情况下操作系统针对线程的调度是随机的。可是要实现公平锁需要付出额外的东西比如需要使用一个队列记录一下各个线程获取锁的顺序等。synchronized是非公平锁。synchronized自适应的过程——锁升级从上到下顺序如下当然只能升级不能降级。那么偏向锁是什么偏向锁它本质上是一个懒汉模式进行synchronized刚一上来不是真加锁而是简单做一个标记这个标记非常轻量相比于加锁解锁来说效率高很多。如果没有线程来竞争这个锁最终当前线程执行到解锁代码也就只是简单清除上述的标记即可不涉及真加锁真解锁。如果有其他线程来竞争就抢先一步在另一个线程拿到锁之前抢先拿到锁一旦加锁偏向锁就会变成轻量级锁其他线程只能阻塞等待。因此整个升级过程1无锁到偏向锁——代码进入synchronized板块2偏向锁到轻量级锁——拿到偏向锁的线程运行过程中遇到了其他线程竞争这个锁3轻量级锁到重量级锁——JVM发现当前竞争锁的情况非常激烈锁消除这也是编译器优化的一种体现。编译器会判定当前这个代码逻辑是否真的需要加锁如果确实不需要加锁但是你写了synchronized就会自动把synchronized去掉。当它100%确定你这个代码是单线程的才会真正触发消除像一些判定不清楚的情况是不会触发的因此我们不应到处synchronized不然优化机制只能把其中一部分能明确判定的优化掉还会有很多不应该使用但是编译器也优化不调。目前还是不能完全依赖编译器优化。锁粗化加锁和解锁之间包含的代码越多不是代码行数而是实际执行的指令与时间就认为锁的粒度就越粗反之就越细。一个代码中反复针对细粒度的代码加锁就可能被优化成更粗粒度的加锁。如图多次加锁与解锁粗化为在开始与结束时加锁与解锁。当然也不是说锁的粒度越粗就越好锁粗了就会影响到线程的并发程度。如果确实三件事本身都需要加锁粗化成一把锁是合理的但如果三件事里两件事需要加锁另一件事不需要此时粗化就把能并行执行的事情也变成串行了。比如下面这个就可以粗化CAS的比较与切换多线程中有个叫CAS的日常开发中很少用但很多地方有他的影子。下图是它的执行过程当然CAS最重要的用途就是实现原子类使用原子类的目的就是避免加锁。原子类专有名词特指atomic这个包里的类。之前谈到的通过synchronized保证一个修改的原子性和“原子类”这样的术语是不相关的。今天就到这里明天我们继续。我的gitee链接https://gitee.com/QQ2240635095/java4_10.git