乐观锁
总是假设最好的情况,每次使用数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在次期间别人有没有去更新这个数据,如果没有更新则可以进行更新操作,如果有更新可以不断的尝试(自旋)直到成功。
实现方式:版本号机制,CAS算法
版本号机制:在数据表中加一个版本号version字段,表示数据被修改的次数,数据每次被修改之后version+1.当线程要更新数据的时候,读取数据的同时也会读取version值,只有数据的version值和要更新字段的version值一致的时候才进行更新。
CAS算法:比较并交换,有三个操作数,需要操作的变量的内存值,进行比较的值,将要更新的值。当且仅当内存中的值和预期值相同的时候,才将该内存地址的值更新为目标值。若失败则自旋直到成功。Java中juc包下的原子类就是通过CAS算法实现的。CAS主要是通过Unsafe类来实现的。
CAS算法的缺点:
- ABA问题,AtomicStampedReference带有版本号的原子引用类解决这个问题。在进行CAS操作的时候,不仅要比较内存地址的值和预期值是否相等,还要比较版本号和预期版本号是否相等,若相等则修改变量的值及版本号,若不相等进行自旋操作。
- 长时间自旋的话CPU开销较大。
- 只能保证一个共享变量的原子操作,提供AtomicReference,可以将多个共享变量合并成一个变量然后用使用AtomicReference将其变成原子类。
悲观锁
总是假设最坏的情况,每次使用数据的时候别的线程会对数据进行修改,所以每次使用数据的时候都会上锁,这样别的线程想要获取数据的时候将会阻塞直到拿到锁。数据库中的行锁、表锁、读锁、写锁都属于悲观锁。Java中的synchornized和ReentrantLock都属于悲观锁。
应用场景
乐观锁使用于读多写少的情况,省去了锁的开销,加大了整个系统的吞吐量
悲观所适用于写比较多的情况,如果冲突发生比较多,乐观锁会一直自旋,降低系统的性能。
偏向锁/轻量级锁/重量级锁
偏向锁:当一个线程获取锁之后,不存在其它线程对这个锁的竞争,那么线程是不需要同步的,这种情况下该锁就被设置成偏向锁,线程不需要进行同步。当有其他线程来竞争这个锁的时候,将撤销偏向锁,升级为轻量级锁。
轻量级锁:使用CAS操作来获取锁,每个栈帧中会有锁记录,锁记录复制了对象头中的markwork。使用CAS获取锁,就是尝试将对象头中的markwork指向锁记录对应的地址,若成功,则表明获取该锁。若失败,自旋获取。当释放锁的时候将markwork指向对象头,如果成功则表明没有发生锁竞争,若失败,则表明存在锁竞争,膨胀为重量级锁。
重量级锁:轻量级锁解锁失败膨胀为重量级锁。