悲观锁(Pressimistic Locking)
加锁悲观策略:
并发控制而言,锁是一种悲观策略,会阻塞线程执行,本质上是将并发转变为串行来实现的,势必会影响吞吐量。而且线程的数量是有限的,依赖于操作系统,而且线程的创建和销毁带来的性能损耗是不可以忽略掉的。虽然现在基本都是用线程池来尽可能的降低不断创建线程带来的性能损耗。
对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。例如: select * from table_name where id = "xxx" for update
这样查询出来的这一行数据就被锁定了,在这个update事务提交之前其他外界是不能修改这条数据的,但是这种处理方式效率比较低,一般不推荐使用。
乐观锁(Optimistic Locking)
无锁乐观策略:
它会假设对资源的访问时没有冲突的,既然没有冲突就不需要等待,线程不需要阻塞。那多个线程共同访问临界区的资源时,无锁的策略采用一种比较交换技术CAS(compare and swap)来鉴别线程冲突,一旦检测到冲突,就会重试当前操作指导没有冲突为止。
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。
乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
乐观锁的实现:
update account set field1 = ? and field2 = ? and version = version + 1 where version = ?
对比:
与锁相比,CAS会使得程序设计比较负责,但是由于其优越的性能优势,以及天生免疫死锁(根本就没有锁,当然就不会有线程一直阻塞了),更为重要的是,使用无锁的方式没有所竞争带来的开销,也没有线程间频繁调度带来的开销,他比基于锁的方式有更优越的性能,所以在目前被广泛应用,我们在程序设计时也可以适当的使用.
经验总结:
不过由于CAS编码确实稍微复杂,而且jdk作者本身也不希望你直接使用unsafe(后面会讲到)来进行代码的编写,所以如果不能深刻理解CAS以及unsafe还是要慎用,使用一些别人已经实现好的无锁类或者框架就好了。
CAS算法:
一个CAS方法包含三个参数CAS(V,E,N)。V表示要更新的变量,E表示预期的值,N表示新值。只有当V的值等于E时,才会将V的值修改为N。如果V的值不等于E,说明已经被其他线程修改了,当前线程可以放弃此操作,也可以再次尝试次操作直至修改成功。基于这样的算法,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰(临界区值的修改),并进行恰当的处理。
es
_version : 第一次创建一个document的时候,_version内部的版本号就是1,以后的每一个修改(包括删除)都会加1
curl -XPUT 'localhost:9200/blog/post/8?pretty' -H 'Content-Type: application/json' -d'
{
"title": "My BBB first blog entry"
}'
{
"_index" : "blog",
"_type" : "post",
"_id" : "8",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : true
}
update操作:下面相当于全量替换,
这个时候_version = _version+1
curl -XPUT 'localhost:9200/blog/post/8?pretty' -H 'Content-Type: application/json' -d'
{
"title": "My BBB first blog entry"
}'
{
"_index" : "blog",
"_type" : "post",
"_id" : "8",
"_version" : 2,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
删除后_version = _version+1
curl -XDELETE 'localhost:9200/blog/post/8?pretty' -H 'Content-Type: application/json'
{
"found" : true,
"_index" : "blog",
"_type" : "post",
"_id" : "8",
"_version" : 3,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
}
}
curl -XPUT 'localhost:9200/blog/post/8?pretty' -H 'Content-Type: application/json' -d'
{
"title": "My first blog entry add again"
}'
这时候的delete不是立马物理删除
版本控制携带version参数
官网说的很详细:
https://www.elastic.co/guide/cn/elasticsearch/guide/current/optimistic-concurrency-control.html
curl -XGET 'localhost:9200/blog/post/8?pretty&version=4'
{
"_index" : "blog",
"_type" : "post",
"_id" : "8",
"_version" : 4,
"found" : true,
"_source" : {
"title" : "My first blog entry add again"
}
}