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