开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 19 天,点击查看活动详情
前言
大家在开发并发程序的时候,一定会想到一种方法来控制多线程对共享变量的访问,那就是加锁。不过,在 Java 中,锁大体上有两类:悲观锁和乐观锁。今天,我们就一起来看看这两种锁的定义、区别和实现方式吧~
悲观锁的含义
所谓悲观锁,就是总假设最坏的情况,每次去获取数据时,都认为数据会被修改,所以每次在获取数据时都会加锁,这样如果是其他线程想获取这个数据,就会阻塞直到它拿到锁。
共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。
悲观锁的实现方式
在我们的开发中,常常会遇到以下两种实现方式:
- 像我们开发时常用的数据库 MySQL,就用到了很多悲观锁机制,如行锁、表锁、读锁、写锁等
- 在 Java 中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现
乐观锁的含义
所谓乐观锁,就是总假设最好的情况,每次获取数据的时,都认为数据不会被修改,所以不会上锁,但是在更新的时候会判断在此期间是否有去更新这个数据,可以使用版本号机制和 CAS 算法实现。
乐观锁适用于多读的应用类型,这样可以提高吞吐量
乐观锁的实现方式
乐观锁常见的两种实现方式
- 版本号机制
- CAS 算法
版本号机制
一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数,当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
CAS 算法
比较与交换是一种有名的无锁算法。在不使用锁的情况下,实现多线程之间的变量同步,即在没有线程被阻塞的情况下实现变量的同步。CAS 算法涉及到三个操作数:
- 需要读写的内存值 V
- 进行比较的值 A
- 拟写入的新值 B
当且仅当 V = A 时, CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作 (比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了 CAS 实现的。