(一)不同类型的锁的概念以及一些名词的定义

一、乐观锁和悲观锁思想

  悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。

  悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。 Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。

  乐观锁的思路一般是表中增加版本字段或时间戳等,更新时where语句中增加版本的判断,算是一种CAS(Compare And Swep)操作,商品库存场景中number(库存量)起到了版本控制(相当于version)的作用( AND number=#{number})。

二、同步锁,互斥锁  

  同步是是依赖于对象而存在,"对于每一个对象,有且仅有一个同步锁;不同的线程能共同访问该同步锁。但是,在同一个时间点,该同步锁能且只能被一个线程获取到。不同线程对同步锁的访问是互斥的,不同的加锁方式不互斥",协调多个相互关联线程合作完成任务,彼此之间知道对方存在,执行顺序往往是有序的。

  互斥是通过竞争对资源的独占使用,彼此之间不需要知道对方的存在,执行顺序是一个乱序。

三、独占锁、共享锁

  (01) 独占锁 -- 锁在一个时间点只能被一个线程锁占有。根据锁的获取机制,它又划分为“公平锁”和“非公平锁”。公平锁,是按照通过CLH等待线程按照先来先得的规则,公平的获取锁; 而非公平锁,则当线程要获取锁时,它会无视CLH等待队列而直接获取锁。独占锁的典型实例子是ReentrantLock,此外,synchronized,ReentrantReadWriteLock.WriteLock也是独占锁。 独占锁0是未被持有,1或者其他代表被线程占用。

  (02) 共享锁 -- 能被多个线程同时拥有,能被共享的锁。JUC包中的ReentrantReadWriteLock.ReadLock,CyclicBarrier, CountDownLatch和Semaphore都是共享锁。

四、可重入锁、不可重入锁

  可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。(ReentrantLock,synchronized都是可重入的)

  不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。

四、偏向锁,轻量级锁,自旋锁,重量级锁   

  • 偏向锁(加synchronized的代码在单线程条件下)

    轻量级锁是在没有锁竞争的情况下,使用cas 操作 去除互斥的操作。那么偏向锁就是在没有锁竞争的情况下,连cas都不用了。锁会偏向第一个获取它的那个线程。hotpost作者认为,在大多数情况下是没有锁竞争的,并且同一个线程 会重复获得这一把锁。添加偏向锁 就是为了让获得锁的代价更低。当一个线程获取到锁的时候,会使用cas操作,把当前线程的id记录到锁对象的markword中。以后这个线程在进入和退出这个同步块的时候,只需要检查下markword中的线程id 是不是自己就可以了。当有另外一个线程去尝试获得锁的时候,偏向锁就宣告结束。 

  • 轻量级锁

    代码进入同步块的时候,如果对象没有被锁定 虚拟机会在当前栈帧下创建一个lock record的锁记录空间,用来存储锁对象 markword的拷贝。然后虚拟机将使用cas操作将markword更新为指向lock record的指针,如果更新成功 那这个线程就拥有了锁。并更新锁对象的markword 为00 ,表示锁对象处于轻量级锁。如果更新失败,就说明有其他线程竞争这个锁,如果有2条线程以上,轻量级锁就会升级为重量级锁。在没有锁竞争的情况下 使用cas 操作,就避免互斥的开销。

  • 自旋锁

    当线程在获取锁的时候,如果发现锁已经由其他线程获得,这时候线程会先进行自旋,就是循环。如果在循环次数内这个线程得到了锁,那么他就进入。如果没有就转到轻量级锁

  重量级锁

    轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

五、分布式锁

   用于分布式,多进程条件协同工作的原理。

六、AQS,CLH队列,CAS函数

  1. AQS -- 指AbstractQueuedSynchronizer类,(队列同步器)
    AQS是java中管理“锁”的抽象类,锁的许多公共方法都是在这个类中实现。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。 AQS是独占锁(例如,ReentrantLock)和共享锁(例如,Semaphore)的公共父类。 AQS为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法, 这些方法定义了state是如何被获取或释放的。鉴于此,本类中的其他方法执行所有的排队和阻塞机制。子类也可以维护其他的state变量,但是为了保证同步,必须原子地操作这些变量。
  2. AQS锁的类别 -- 分为“独占锁”和“共享锁”两种。
    (01) 独占锁 -- 锁在一个时间点只能被一个线程锁占有。根据锁的获取机制,它又划分为“公平锁”和“非公平锁”。公平锁,是按照通过CLH等待线程按照先来先得的规则,公平的获取锁; 而非公平锁,则当线程要获取锁时,它会无视CLH等待队列而直接获取锁。独占锁的典型实例子是ReentrantLock,此外,ReentrantReadWriteLock.WriteLock也是独占锁。 独占锁0是未被持有,1或者其他代表被线程占用。 (02) 共享锁 -- 能被多个线程同时拥有,能被共享的锁。JUC包中的ReentrantReadWriteLock.ReadLock,CyclicBarrier, CountDownLatch和Semaphore都是共享锁。
  3. CLH队列 -- Craig, Landin, and Hagersten lock queue
    CLH队列是AQS中“等待锁”的线程队列。在多线程中,为了保护竞争资源不被多个线程同时操作而起来错误,我们常常需要通过锁来保护这些资源。 在独占锁中,竞争资源在一个时间点只能被一个线程锁访问;而其它线程则需要等待。CLH就是管理这些“等待锁”的线程的队列。 CLH是一个非阻塞的 FIFO 队列。也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性。
  4. CAS函数 -- Compare And Swap 
    CAS函数,是比较并交换函数,它是原子操作函数;即,通过CAS操作的数据都是以原子方式进行的。例如,compareAndSetHead(), compareAndSetTail(), compareAndSetNext()等函数。 它们共同的特点是,这些函数所执行的动作是以原子的方式进行的。java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。 synchronouse是一种悲观锁,它会导致其他所有需要锁的线程挂起。 CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 CAS采用的是一种非阻塞算(nonblocking algorithms),一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。 CAS通过调用JNI的代码实现Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。 JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。
  CAS会出现的问题
    ABA(使用版本号解决) 循环时间长,开销大。(虚拟机pause指令) 只能保证一个共享变量的原子操作。(加锁)

 七、volatile

  解决内存可见性的问题,会使得所有'对volatile变量的读写都会直接刷到主存,即保证了变量的可见性'。

 

posted @ 2020-02-18 10:23  是鑫哥哥啊  阅读(562)  评论(0)    收藏  举报