为什么需要锁
我们把多个线程竞争处理的资源称为临界资源(代码块、方法体等),当一个线程获得了临界资源的使用权以后,为了保证临界资源在同一时间只能由一个线程获得,其它线程必须等这个线程处理完以后才能通过竞争再次尝试获得临界资源的使用权。
追问:为什么要保证临界资源在同一时间只能由一个线程使用?
首先肯定是有这样的场景存在,多个线程同时操作临界资源会导致数据的不一致。
追问:什么叫数据的不一致?
就是你本来期望是一个结果,但是偏偏变成了另一个结果,本来不应该出现的情况,但是偏偏出现了,就叫数据不一致了。
追问:所以归根结底是为了解决临界资源在并发使用过程中可能出现的数据不一致性问题才必须保证临界资源在同一时间只能由一个线程获得?
是的。因为并发会导致数据不一致,那干脆在使用临界资源的时候不要并发了,多么简单暴力啊。
追问:说了半天还是没有说到锁啊?
那我们如何实现上面说的 必须保证临界资源在同一时间只能由一个线程获得呢?答案就是大家对向某个人提出申请,这个人必须足够公正,能保证在同一时间只能有一个人获得申请,而且要保证获得申请的人不撤销申请其他人即使再申请也不会获得申请,这个人就是锁,要知道这个人一旦被看作锁,那么他也将变成临界资源,申请锁的这个动作在多线程里必须也得能保证数据一致,也就是线程安全,貌似陷入死循环了。但这个保证是操作系统保证的,更确切的说是操作系统的内核保证的,操作系统毕竟还是一个软件,内核要保证线程安全本质上是通过硬件完成的,其实就是锁总线。
锁的分类
锁并不是某个语言特有的,c语言也有锁,c++也是,包括一些后现代的编程语言例如scala、groovy等都有锁,操作系统更需要锁,redis有锁,数据库有锁,分布式系统有分布式锁。锁你可以理解为一个工具类,使用这个工具类可以保证临界资源在同一时间只能由一个线程获得。锁的种类有很多,真的很多,有悲观锁、乐观锁,有互斥锁、共享锁,有可重入锁、不可重入锁,数据库有行锁、表锁、页锁,分布式有分布式锁,可以用redis实现可以用zookeeper实现。按照我的理解和记忆方案,不见得是对的,但在这里提出来,大家一起讨论一下。
我认为只要是锁一定都是悲观锁,因为乐观锁虽然叫锁,但是其实并不是锁,只是后人为了区分而强加的概念。乐观锁的实现原理逃不出CAS,悲观锁的原理逃不出互斥量。所以Java中所有叫锁的都是悲观锁包括 synchornized。
然后就是锁的几个特性,比如 公平不公平啊、可重入不可重入啊、独享啊还是共享啊。每个锁必然有这三个特性。
锁分类
悲观锁/乐观锁
重点讲两类锁的思想区别 和 cas
锁特性
公平性/非公平性、公平锁/非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。
非公平锁不按照申请顺序,后申请的可能会先获得锁。
对于Java的ReentranLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
ReentranLock中有一个内部类:Sync。FairSync、NonFairSync继承了Sync,公平与不公平在源码体现在【!hasQueuedPredecessors()】这句代码,公平的话:如果前面有等待节点,那就不去获得锁;不公平就是抢占;具体看:https://blog.csdn.net/lsgqjh/article/details/63685058
对于 Synchonized而言,也是一种非公平锁,由于其不像 ReentrantLock是通过AQS实现的,所以没办法变成公平锁。
可重入性/不可重入性、可重入锁/不可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁,不会因为之前已经获取过还没释放而阻塞。
Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
到目前为止我还没有遇到过不可重入的锁,也想象不到不可重入锁的使用场景,因为不可重入锁大概率会出现死锁情况,所以貌似没有人会去实现不可重入锁。
独占性/共享性、互斥锁/读写锁
独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。这里主要是为了把一个操作掰成两个操作,一个读一个写,分别加不同粒度的锁,目的是实现在只有读的场景下的最大并发。
独享锁也叫互斥锁,读写锁等同于一个独享锁+一个共享锁,也叫一个写锁+读锁。读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。
对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
对于Synchronized而言,当然是独享锁。
另外在synchronizd关键字的优化相关知识里面,会碰到 诸如 偏向锁/轻量级锁/自旋锁/适应性自旋锁/重量级锁 的概念,这些都是锁升级中的某些状态,除了重量级锁是锁外,其它要么不是锁,要么就是在成为锁的路上,这里不过多讨论,只需要记住只要是锁一定有上面三个特性就好。所以synchronized的全称应该叫 非公平的可重入的互斥悲观锁。同理,ReentrantLock 默认也是非公平的可重入的互斥悲观锁,当然ReentrantLock也可以实现 公平的可重入的互斥悲观锁。