美文网首页
锁的概述

锁的概述

作者: 凉风拂面秋挽月 | 来源:发表于2020-02-25 19:53 被阅读0次

乐观锁与悲观锁

悲观锁

乐观锁和悲观锁的概念出自数据库,但在java并发包中也引入和类似的概念(乐观锁/悲观锁是一个概念而不是特指某一种锁,所以你可以说CAS是乐观锁的实现,Synchronized是悲观锁的实现,而不能说CAS就乐观锁)

悲观锁指对数据被外界修改持保守态度,认为数据很容易被其他线程修改,所以在数据被处理前就对数据进行加锁,并在整个处理过程中使数据始终处于锁定状态。

sql语句中的“select ...... for update”即为悲观锁的实现,只有该语句的事务执行完成,才会释放该语句所锁定的行,如以下示例

public int updateEntry(int id){
   (1)
   EntryObject entry = query("select * from table where id = #{id} for update",id);
   (2)
   String name = generatorName(entry);
  entry.set(name);
   .....
   (3)
   int count  = update("update table set name=#{name} where id = #{id}",entry)
return count
}

程序调用updateEntry开启一个事务,执行query查询一条记录,事务传播特性为requried,所以执行entry没有开启新事务,而是加入updateEntry事务,同理2,3均为同一事务,query的查询到update执行完才被提交。
这时如果有多线程调用updateEntry方法,且为同一id,只有一个线程执行代码1会成功,其他线程被阻塞,这是因为同一时间只有一个线程可以获得记录的锁,在该线程的方法执行结束前,其他线程均被阻塞在query方法中。

这是数据库使用for update悲观锁的示例。
在java中通过Synchronized(this.name){}这种操作和ReentrantLock.lock这种独占锁就见得多了,不举例了。所有独占锁都是悲观锁。

乐观锁

乐观锁是相对于悲观锁而言,它认为数据一般情况下不会造成冲突,所以在访问记录前不会加独占锁,而是在数据进行正式更新的时候才会对数据是否冲突进行检测(正如CAS操作中,在更新的前一步对内存中的数据进行比较,如果是预期的值,说明其他线程没有对其更改,更新就行,如果不是预期值,通过while循环再次获得最新的内存值,再次CAS更新,retry)。
乐观锁没有加锁,自然没有锁的开销,对冲突不高的场景效率很高。

在数据库中的乐观锁,并没有通过sql语句实现的方式,通常是通过在表中添加version字段,将version作为判断数据是否被修改的标准,其实思路和CAS相同。

数据库中的乐观锁使用如下

public int updateEntry(int id){
   (1)
   EntryObject entry = query("select * from table where id = #{id} ",id);
   (2)
   String name = generatorName(entry);
  entry.set(name);
   .....
   (3)
   int count  = update("update table set name=#{name} version=${version}+1 where id = #{id} and version=#{version}",entry)
return count;
}

当多线程同时执行updateEntry方法时,多个线程可以同时执行(1)(2)不会产生阻塞。但由于(3)中增加了一条判定条件version=#{version},假如t1线程执行(3)成功,则数据库中version+1,其他线程在随后执行(3)的时候,发现version判定失效,那么执行(3)失败。

公平锁和非公平锁

根据线程获得锁的抢占机制,锁可以分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按线程请求时间的早晚决定的,也就是先来先得,后来排队。非公平锁是运行时闯入,先来不一定先得,看谁抢的快。
ReentrantLock默认时非公平锁,可通过传入参数设置
ReentrantLock lock = new ReentrantLock(true);//公平锁

ps:在没有公平性要求的情况下,尽量使用非公平锁,因为公平锁会带来性能上的开销。

独占锁和共享锁

根据锁是否可以被多个线程持有,将锁分为独占锁和共享锁,ReentrantLock是独占锁
CountDownLatch是共享锁。独占锁是悲观的,共享锁是乐观的。

可重入锁

当一个线程要获取其他线程持有的独占锁时,会被阻塞,但如果线程想获得的是自己已经获得的锁呢?如果这种情况是可以的,称为可重入,否则称为不可重入。
例子

public class hello{
    public synchronized void helloA(){
         System.out.println("helloA");
   }
public synchronized void helloB(){
         System.out.println("helloB");
         helloA();
   }
}

如上代码中调用hello方法会获取内置锁,然后打印语句,然后去helloA再次获得内置锁,如果内置锁是不可重入的,则程序会一直阻塞,因为该锁还被helloB占据。而经测试,程序执行未阻塞,则synchronized内部锁为可重入锁。

自旋锁

由于java中线程是与操作系统中的线程是一一对应的,所以线程在获得锁失败后,会被切换到内核态挂起。当线程获得锁时又要切换到用户态,这种内核/用户态的切换开销比较大,影响并发性能。
自旋锁可以一定程度减缓这个问题,如果当前线程发现锁被其他线程持有,不会立即切换到内核态阻塞自己,而是多次尝试获取该锁(默认为10次),如果还不能获得该锁,再切换状态。
自旋锁是使用cpu时间换取线程阻塞和内核调度的开销。

相关文章

  • 锁的概述

    乐观锁与悲观锁 悲观锁 乐观锁和悲观锁的概念出自数据库,但在java并发包中也引入和类似的概念(乐观锁/悲观锁是一...

  • MySQL锁详解

    Mysql****锁机制 锁概述 锁分类 MySql锁 目的了解锁的用途了解锁的危害根据锁的一个概述进一步了解优化...

  • 4:Mysql的事务隔离级别和锁机制原理

    1:概述 2:事务及其ACID属性(概述) 3:锁详解 4:会话与事务的关系 5: 表锁 6: 行锁 7:小总结

  • mysql 锁概述

    行锁 shared Locks 共享锁 也叫读锁 Exclusive Locks 排他锁 也叫写锁 表锁 意向锁 ...

  • 总结Mysql中的锁

    MySQL中的锁 概述 MyISAM支持表锁,InnoDB支持表锁和行锁,默认为行锁 表级锁:开销小,加锁快,不会...

  • mysql锁机制

    1,数据库锁理论概述分类: 读锁、写锁。行锁,表锁,页锁。1.1 表锁:无死锁,加锁快加锁:lock tabl...

  • MySQL锁

    锁 一. 锁概述 InnoDB默认支持行级锁,但也支持表级锁MyISAM,Memory支持表级锁 三种锁特性 1....

  • 2019-04-24_Object与内部锁的使用基础(一)

    Object与内部锁的使用基础1.概述锁可以划分无锁、偏向锁、轻量级锁、重量级锁1.1.锁的对比1.内部锁Sync...

  • Java 多线程(六)- 常用同步类

    ReentrantReadWriteLock 概述 严格来说 ReentrantReadWriteLock 是锁,...

  • MySQL 锁之二——表锁

    1、概述 MySQL 表级锁是以单个表为粒度的锁,InnoDB 和 MyISAM 引擎都支持表级锁; 2、表锁分类...

网友评论

      本文标题:锁的概述

      本文链接:https://www.haomeiwen.com/subject/evoxchtx.html