悲观锁:总是假设最坏的情况(数据已经被修改,适用于经常写的情况),每次去拿数据的时候都会认为别人修改,所以,每次在拿数据时都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁(共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程),传统的关系型数据库里面就用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在做操作之前先上锁,例如(synchronized和ReentrantLock)等独占锁就是悲观锁思想的实现。
乐观锁:总是假设最好的情况(适用于经常读的情况)每次拿数据都认为别人不会修改,所以不会上锁,但在更新的时候会判断一下在此期间别人没有去更新这个数据,可以通过版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
1.版本号机制
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时version值加一。当线程A要更新数据时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才会更新,否则重试更新操作,直到更新成功。
2.CAS算法
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步
CAS算法涉及到三个操作数:
需要读写的内存值V(想要改变的数)
进行比较的值A(原来的数)
拟写入的新值B(想变成的数)
当且仅当V的值等于A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是原子操作),一般情况下是一个自旋操作,即不断的重试。
但是会出现ABA问题,
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查它仍然是A值,那我们就能说明这个值没有被别的线程修改吗?因为这段时间它的值可能被修改成其他值,然后又改回A,那么CAS就误认为它从来没有被修改过,这个问题被称为CAS的ABA问题
jdk1.5之后的AtomicStampedReference类提供了解决方案,其中的CompareAndSet方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和改标志的值设置成给定的更新值。
Synchronized很久之前人们称之为“重量级锁”,但是在javaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏量锁和轻量级锁,以及其他各种优化后,性能提高了很多。
synchronized的底层实现主要是依靠Lock_Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。