(一)不同类型的锁的概念以及一些名词的定义
一、乐观锁和悲观锁思想
悲观锁(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函数
七、volatile
解决内存可见性的问题,会使得所有'对volatile变量的读写都会直接刷到主存,即保证了变量的可见性'。