序
本来对理性世界里的代码总是夹杂着崇敬的心,因为它们看起来总是绝对的正确和感性。但自从看到乐观和悲观用于形容SQL中的锁之后,一切都有所变化。它们好像也有喜怒哀乐,也会感性,也会极端,还会不开心。我想看看它们的开心和不开心,看看它们发生了什么事,让绝对理性的事物竟然染上了人类独有的情感。最近看了几本小说,想试着用半比喻的形式对乐观、悲观锁两个概念进行阐述。
我是锁
锁的独白
我是锁,我存在于这个世上已久。人们会用我来保护他们的心头爱,不管是好是坏,我都照单全收,从不拒绝。自从计算机问世,用的人成几何倍数增长,我也以数字化的形式进入到了二进制的世界里。用二进制的形式来保卫二进制。不论以什么样的形式存在,被我保护的东西都会原封不动。至少,不会让和主人同等级别的人有机会趁虚而入对其做手脚。别人想要看到我保护的东西,就必须和主人申请,钥匙也好,申明也罢。我一直就是这样。不过,主人的要求越来越奇怪,我是一个绝对的锁,不允许我的世界里出现模棱两可的东西。我向主人抱怨,希望他不要破坏我的世界观,他总是需求是这样,我也没办法啊。我越想越气。我突然发现,我也有了情绪,变得有点奇怪。
锁的定义
锁,又名Lock。人尽皆知,但今天要聊的不是锁门的也不是怎么撬锁。只说说程序员天天交到的数据库里的锁。从锁的字面意思就知道,他是保证安全的,外面的人进不来,里面的人出不去。这里的人其实是数据,锁在数据库只做一件事,保证数据的一致性。也就是,哪怕千军万马都急着用锁保护的东西,那么,也要有个先来后到,一个一个来。这样保证了数据在任意时刻只会右一个线程进行操作,从而不会导致数据读取和写入的不准确。
乐观和悲观
感性的锁
主人在不停的灌输给我,作为一个锁,除了黑白两面之外,也应该带点灰。就像主人写的代码,有人会说好,有人会说辣鸡,也会有人说还行。这也让我有了一点感觉。就算在二进制的世界里,你碰见的都是0和1,但也有轻重缓急之分。有的0不急,说你慢慢来,只要安全就可以了。也有的1很着急,说搞快点,慢了下次就没我的饭吃了。如果我都用同一个自己去面对所有的0和1,那么我一定活不到下一次版本更新。于是,我瞧瞧做了两个我的分身,他们和我都拥有同样的管控权利,只是按照0和1还有代码的需求,他们有了自己的性格,或者说我用人类的情绪强化了他们,让他们用人类的情绪和思维方式来控制我们保护的数据。
为什么要悲观和乐观
应用的场景不同,需要的人性格也有所不同,需要励志和激情的时候,你要是找个闷灌,可能你就完蛋了。程序也是如此,如果程序等着对数据进行访问(读操作多),而进行更新操作的情况较少,那么,我们不必把锁的管控做的过于严苛,让锁和乐观的人一样自信且坚定的相信所见即真实。简言之,读多写少,这是乐观锁的工作场景。这样做,则能够保证所有程序可以在同一时刻都读取数据,而更新数据时,仅根据数据版本、更新时间来对数据的实时性进行验证,并不会对同时有几个人在进行写操作进行限制。这里需要提一下乐观锁的实现方式,一般采用CAS(Compare and Swap)来实现,对于内存中的某一个值V,提供一个旧值A和一个新值B。如果提供的旧值V和A相等就把B写入V,这个过程是原子性的。
如果你遇到了一些严谨的工作,那么,找一个理性且带有悲观主义色彩的人来做,会更靠谱一些。因为很多时候,他们更倾向于相信事件会朝着坏的方向发展。程序在一些注重数据准确性而非效率的场景出现时,使用悲观锁是有必要的。虽然,他们每次都会保证数据在被一个线程写,但这也会拖慢整个程序的效率。写多读少,是悲观锁的工作场景。
乐观锁和悲观锁家族
乐观锁和悲观锁的对话
乐观锁:”现在的数据人们都懂得怎么使用和安排了,想开点老兄,管理好你现在的数据就好,不要太干涉0和1自身的操作了。”
悲观锁:”你说的轻松。如果出现了忒休斯之船的问题,你怎么办。所以说,保险起见,还是要对这些数据的操作进行严格的把关,想要操作数据,就必须一个一个来!”
于是,悲观锁像锁一样,创造了排它锁和共享锁,帮他分担严格管控数据的工作。
顾名思义,排它锁成了仓库的老大,严格按照先来后到,让数据保证每次只接触到一个指令。
而共享锁稍微温柔一些,他还是允许大家一起访问,但是,在写数据时,严格遵守先来后到的规矩。
排他锁和共享锁
排它锁使用时,按照线程上锁的顺序,对资源进行管控,如果一个线程为资源上了排它锁,那么,其他的线程既不能读取数据也不能写入数据,同时其他线程无法再为该资源添加其他锁,直到线程使用结束。
共享锁使用时,则可以同时读取数据,但只能单个线程写入数据,允许其他线程为该资源加共享锁,但不能加排它锁。
转折
意外的死锁
打起来了,打起来了。0和1们纷纷到仓库看热闹,只见两把锁纠缠在一起,0和1们需要访问的数据都被各自保护,各自无法进入想要访问的数据里去。这是锁出来,对没错就是开头的那把锁,在计算机世界里,他算是所有锁的神。
他问:”你们为何要为难各自的访问者?”
两把扭打着的锁说着:”他们的人要抢我保护的数据!”
锁沉默了,思索片刻。他问:”是不是你们两个各自保护的东西,在各自主人还未使用完之后,又强行被各自的主人霸占了,而主人又想用对方的东西?”
他们一口同声的说:”对!”
于是,锁笑了笑,把他们拆开,结束了这次的操作。
为什么是死锁
死锁的原理其实很简单,两把锁各自保护的东西在两个线程操作时,发生了重叠。可以理解为,A线程在使用A资源,同时又准备使用B资源,而B线程正在使用B资源,同时准备使用A资源。那么,彼此线程都不能得到满足,因为A和B资源都被占用了,那么A和B线程就没办法正常结束,所以,所有线程都没有办法正常的对资源进行使用。
怎么解决
解决死锁最简单的方法就是:不要贪心,在一个线程或者一个事务里不要想着占用所有的资源,不要把所有耗时的操作都放在一个线程或事务里处理并且使用的资源又都是被高频使用的。其次就是,对同一个资源尽量设计同样的访问步骤,保证每个到达资源所需要的耗时相近,以便于给上一个线程留出时间差进行处理。
尾
锁成为了神,他也具备了一些人类独有的情感,他不在执著是非。他也开始相信存在即合理。