锁 2020-12-30

一: synchronized的执行过程: 偏向锁->轻量级自旋锁->重量级锁
  1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁

  2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1

  3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。

  4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁

  5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

  6. 如果自旋成功则依然处于轻量级状态。

  7. 如果自旋失败,则升级为重量级锁。

首先试图加偏向锁,修改对象头的线程id,如果失败,说明存在竞争,进而使用轻量级锁进行自旋获取锁,自旋失败,则升级为重量级锁


二: 锁的类型
捕获.PNG
  1. 公平锁和非公平锁

公平锁是严格按照请求所的顺序来排队获得锁的,而非公平锁时可以抢占的

  1. 轻量级锁和重量级锁

    轻量级自旋 不进行操作系统用户态和内核态的转换,消耗cpu;重量级锁相反

  2. 读写锁和互斥锁

  3. 悲观锁和乐观锁

    悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。 乐观锁:假定不会发生并发冲突,只在提交操作时检测是否违反数据完整性。(使用版本号或者时间戳来配合实现)

  4. 可重入锁 ReentrantLock

    通俗来说:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。

    实现原理:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁。


三: 分布式标识 实现的幂等 VS 锁

标识: 根据标识决定是否执行,无法执行可以返回也可以自旋等待

锁: 对象,资源;

思考:多个请求 可能相同id,可能不同id,要求相同id顺序执行,不同id可以并发??(不是锁对象,而是根据标识判断执行逻辑)

使用id作为标识位来实现,存储了该标识则阻塞等待

所以 幂等:拿到标识执行,拿不到则返回不执行(可以不做)

锁:拿到标识执行,拿不到则阻塞或者自旋等待执行(必须要做)


四: 死锁
  • 互斥条件:一个资源每次只能被一个进程使用。

  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

概括:一个资源每次只能被一个线程;线程没使用完之前,不能剥夺;线程请求其他资源,不会放弃已获取的资源;线程间为等待资源形成循环

避免死锁: 预防 与 后置处理

避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序做操作来避免死锁(阻止循环等待条件,资源排序按顺序获取)


五: CAS AQS(AbstractQueuedSynchronizer类:抽象队列同步器)

AQS: state ThreadId FIFO

  • 获取锁的过程

    1. 当线程调用acquire()申请获取锁资源,如果成功,则进入临界区。

    2. 当获取锁失败时,则进入一个FIFO等待队列,然后被挂起等待唤醒。

    3. 当队列中的等待线程被唤醒以后就重新尝试获取锁资源,如果成功则进入临界区,否则继续挂起等待。

  • 释放锁过程:

    1. 当线程调用release()进行锁资源释放时,如果没有其他线程在等待锁资源,则释放完成。

    2. 如果队列中有其他等待锁资源的线程需要唤醒,则唤醒队列中的第一个等待节点(先入先出)。(释放锁时去唤醒队列中的阻塞线程尝试获取锁**)


你可能感兴趣的:(锁 2020-12-30)