DIY主题讨论8:锁

150 阅读6分钟

【DIY主题讨论8:锁】

1)锁有哪些,分别用来干嘛?

锁来源于数据同时被多客户端修改,五花八门的锁其实就是在特定的场景下给出优化解决方案,解决问题的同时也引入了新的问题。

  1. 乐观锁与悲观锁 乐观锁与区别悲观锁的是加锁心态,悲观锁认为自己使用数据时总会有其他线程修改数据,因此悲观锁采取同步措施,对资源加锁,同一时间只有一个客户端(java里指线程,数据库指连接)可以操作数据,synchronized关键字和Lock的实现类都是悲观锁,数据库事务中的锁也是悲观锁。乐观锁认为自己在使用数据时不会有别的客户端修改数据,所以采取无锁策略,当发生异常(有其他客户端修改数据),则执行配套措施。最为典型的就是CAS(Compare And Swap比较与交换)机制,Java中的CAS充分利用了Java的内存模型特点,线程拥有自己的数据备份,则线程修改自己的数据备份,最后与主线程数据进行比对,如果没有发生变化则更新进去,如果发生变化则重新执行操作。Mysql数据库没有提供乐观锁机制,需要业务或者框架手动实现,同样运用CAS机制,为表中增加version字段,当version发生变化时则需要重复执行。
  • 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
  • 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
  1. 无锁容器,无锁设计核心思路是使对容器的修改可以与读操作同时发生,读者只能看到修改完成的结果。CopyOnWrite模式,修改时先在原数据基础上创建副本,修改副本,修改完成后一个原子操作将副本替换原数据,无锁可以确保读读无锁,读少写的场景下效果会比较好,而并发多写则会带来大量的性能开销,例子:CopyOnWriteArrayList、CopyOnWriteArraySet。CAS发生虎不敢再要重复执行,对于每一次执行开销很大的场景,CAS就不适用了,但是CopyOnWrite模式可以解决这个问题。CAS是以CPU计算代价换取时间性能,而CopyOnWrite则是以空间换取时间性能。
  2. 自旋锁:无锁——偏向锁——轻量级锁——重量级锁 可想而知,当多写时,如果使用CAS机制,那么每次更新很可能就会因为变化重复执行操作,这样显然会大大影响性能。自旋锁可以理解为一种自适应的多级锁,分为四个等级,无线程访问是处于无锁状态,当有一个线程访问时则进入偏向锁(对偏向锁我的理解是偏向锁记录了执行锁的线程,时间片切换后线程获取锁的状态存在可以直接执行)状态,当再有一个线程过来时就发生了竞争,这时就会升级为轻量级锁(CAS),进行有限次数的尝试,如果一直尝试失败,则进入重量级锁,同步单线程执行。
  3. 公平锁与非公平锁 公平锁就是先来后到,先到先服务;非公平锁则允许插队,每次需要进行抢占。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程(来得早不如来得巧)有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
  4. 可重入锁与不可重入锁 可重入锁是同一线程在外层方法获取到锁后,进入内层方法如果是需要同一把锁,则不需要重新获得。不可重入锁则是即使在外层获得锁后,进入内部仍要获取锁,很容易产生死锁
  5. 共享锁与排他锁 共享锁是指该锁可被多个线程所持有,排他锁指该锁只能自己使用。
  6. 分段锁 将大的数据(集合)拆成多段,分段加锁,则操作不同的段就可以同时进行了,这是ConCurrentHashMap早期优化的一种方式。
  7. 闭锁 闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开允许所有的线程通过。当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。CountDownLatch是个典型的闭锁。
  8. 死锁 锁的一种状态,发生必须满足四个条件:资源互斥,请求获取资源并保持不放,没有外界强行剥夺资源,形成循环等待条件。避免死锁,确保一致加锁顺序,引入超时机制,超时剥夺锁。
  9. 活锁 活锁也是锁的一种状态,这种状态下,不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败。解决办法是限定执行次数,强行停止。 2)经典的锁ReentrantLock有哪三行的代码非常变态,找出来,谈谈如何改进。 第131~133行
    int c = getState();
    if (c == 0) {
         if (compareAndSetState(0, acquires)) {

有两个问题,第一个是c遍历很难一眼看出c的含义,第二个getState单纯看名称也很难联想到其含义。 其实state是在父类AbstractQueuedSynchronizer中维护的一个同步状态,用来计数重入次数,state初始值为0,那么c的含义也是重入次数。个人觉得state改为threadNum更易于理解。