乐观锁
就是在操作数据时非常乐观,认为别的线程不会同时修改数据,所以不会上锁;但是在更新数据时会判断在此期间别的线程是否有更新过这个数据
大体流程
- 两个线程,如线程A、B直接获取同步数据,不会加锁,执行各自的操作
- 线程A、B在更新同步资源之前,都会判断资源是否被其他线程修改
- 如果同步资源没有被其他线程修改,那么直接更新内存中同步资源的值
- 如果同步资源被其他线程修改, 那么根据需要执行不同的操作,直接报错或者重试
实现
-
CAS实现
例如Java中java.util.concurrent.atomic包下的原子变量使用了乐观锁的一种CAS实现方式
-
Version版本号机
-
一般在数据表中加上一个数据版本号version字段,表示被修改的次数;当数据被修改时,version的值+1;当线程A要更新数据时,在读取数据的同时也会读取version的值,在提交更新时,若刚才读取到的version值与当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功
update user set name = "zs" ,version = oldVersion+1 where version = "oldVersion"
-
总结
适合读操作多的场景,不加锁的特点能使其读操作的性能大幅提升
悲观锁
操作数据时很悲观,每次去拿数据时都认为别的线程会同时修改数据,所以每次在操作数据时都会上锁,这样别的线程想要操作这个数据时就会被阻塞直到它拿到锁
大体实现
- 多个线程,如线程A、B获取同步锁
- 假设线程A先加锁成功并执行对应的操作,那么线程B只能等待线程A释放锁之后才能操作,线程B处于阻塞状态
- 线程A释放同步锁,然后CPU会唤醒等待的线程,即线程B会再次尝试获取锁
- 线程B成功获取锁,再执行自己的操作
实现
- 传统的关系型数据库使用这种锁机制,例如:行锁、表锁、读锁、写锁等,都是在操作之前先加锁
- Java中的synchronize和ReentrantLock等独占锁
缺点
- 需要阻塞,导致效率低下
- 可能造成某个线程永久等待,即发生死锁的可能性较大
总结
适合并发写操作多的场景,先加锁再执行写操作,能保证写操作的数据正确性