个人博客:https://suveng.github.io/blog/
在学习中经常会接触到各种各样的锁,行级锁、表级锁、页级锁、悲观锁、乐观锁、重入锁、共享锁、排他锁 等等等等。今天就来总结一下这些各种各样的锁。
目前收集到的:行级锁、表级锁、页级锁、悲观锁、乐观锁、重入锁、共享锁、排他锁、公平锁、非公平锁、自旋锁、互斥锁、无锁、偏向锁、轻量级锁、重量级锁
乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。可以通过版本号或时间戳的方式实现。 特点:乐观并发控制相信事务之间的数据竞争的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。
悲观锁,正如其名,它指的是对数据被外界,因此,在整个数据处理过程中,将数据处于锁定状态。 特点:悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。
又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
更新 (U) 锁可以防止通常形式的死锁。一般更新模式由一个事务组成,此事务读取记录,获取资源(页或行)的共享 (S) 锁,然后修改行,此操作要求锁转换为排它 (X) 锁。如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必须等待一段时间,因为一个事务的排它锁与其它事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。由于两个事务都要转换为排它 (X) 锁,并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。 若要避免这种潜在的死锁问题,请使用更新 (U) 锁。一次只有一个事务可以获得资源的更新 (U) 锁。如果事务修改资源,则更新 (U) 锁转换为排它 (X) 锁。否则,锁转换为共享锁。
行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。 特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。 特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。 特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
重进入是指任意线程在获取到锁之后,再次获取该锁而不会被该锁所阻塞。关联一个线程持有者+计数器,重入意味着锁操作的颗粒度为“线程”。 重入锁的实现方式:每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁
Java的线程从 Runnable 状态转向 Running 状态的过程中需要获取时间片,公平锁按进入顺序依次分配时间片,即先来先服务 FIFO 的思想。
非公平锁相对于公平锁,主要体现的是随机性。Java的线程从 Runnable 状态转向 Running 状态时实行抢占机制获取时间片。
自旋锁的核心:不放弃时间片。线程获取不到锁,就会被阻塞挂起,等其他线程释放锁的时候,才被唤醒起来。线程挂起和唤醒是需要转入到内核态完成的,这些操作对系统的并发性能会带来影响。其实有时候线程虽然没法立刻获取到锁,但是也可能很快就会获取到锁。JVM采用了一种叫自旋锁的机制,让获取不到锁的线程执行一个空的循环,一段时间后,如果还是没法获取锁,线程才会被挂起。 如果锁竞争不严重的情况下,且任务执行时间不长,那么可以尝试使用自旋锁。 缺点:如果线程执行的任务需要非常长的时间,或者线程对共享数据的竞争相当激烈,那么使用自旋锁的效率就很低。因为自旋的过程中,一直无法获取到锁,一直在白白的浪费CPU的资源。
保证在同一时刻只有一个线程对其进行操作。比如最常见的 synchronized。
先了解一些关于线程在 JVM 与操作系统层面的基础知识
明确java线程切换的代价,是理解java中各种锁的优缺点的基础之一。
重量级锁 重量级锁,就是让争抢锁的线程从用户态转换成内核态。让cpu借助操作系统进行线程协调。 三种锁的对比