悲观锁
悲观的思想,总认为会出现最坏的情况,在使用的时候总认为数据会被其他途径(来源)修改。那么在使用悲观锁持有数据的时候,悲观锁总会把资源或是数据锁住,其他线程在访问这个资源的时候就会被阻塞,一直到被悲观锁释放。
乐观锁
乐观锁认为数据在被使用的时候总是安全的,所以在读取的时候不会上锁,但在修改的时候会判断当前持有的数据或资源是否被修改过。乐观锁的实现方式通常有两种:版本号机制和CAS实现。相比悲观锁,乐观锁适用于多读的情况,可以有效提高吞吐量。
版本号机制
通过加上version字段实现,每次执行写入操作并且成功后,version +=1。线程在提交数据更新时,会重新读取version的值,只有和更新操作前相等才进行更新,如果不一致,会重新调用更新操作。
CAS算法
compare and swap(比较和交换),CAS是典型无锁型算法,可以在不使用锁的情况下实现多线程之间的变量同步,也就是在线程没有被阻塞的情况下实现变量同步,所以也叫非阻塞同步Non-blocking Synchronization。
CAS中的三个要素:
- 需要读写的内存值V
- 进行比较的值A
- 准备写入的新值B
当且仅当A==V,V=B。预期的A与内存值V相同时将B写入V,否则什么都不做。
自旋锁
提出背景:
多处理器环境中某些资源的有限性,导致需要访问互斥(mutual exclusion),此时引入锁的概念,只有持有锁才能对资源进行访问,在多线程的情况下(不论并发[CPU时间分片]还是并行[同时执行]),同一时刻仅有一条线程持有锁。那么没有持有锁而又需要对资源进行访问的线程就会对自身进行处理,通常有两种处理方式:
1.不阻塞自身,一直循环判断锁是否被释放,称为自旋锁 spin lock。
2.阻塞自身,等待重新调度请求,成为互斥锁 mutex lock。
自旋锁只用自旋(等待-重试)就能重新获取资源,如果持有锁的线程能够短时间内释放资源,那么就可以避免其他线程做用户态和内核态之间的切换,免去了阻塞和切换带来的消耗。
因为自旋锁避免了操作系统进程调度和线程切换,所以自旋锁通常适用于在时间比较短的情况下使用。因此操作系统经常使用自旋锁。
但在长时间不释放锁的情况下,自旋锁非常的消耗性能,它阻止了其他线程的运行和调度。线程持有锁的时间越长,则线程被OS的调度程序中断的风险越大。如果发生在持有锁时中断,其他线程将保持自旋状态直到持有锁的线程释放为止。
自旋锁的优缺点:
由于自旋锁不会造成线程阻塞,对于锁竞争不激烈并且占用锁时间短的情况下性能提升客观。因为自旋的消耗低于线程阻塞挂起再唤醒的消耗,一次挂起和唤醒会造成两次上下文切换 Context Switching。
对于锁竞争激烈或是占用锁时间长的情况下,就不适合使用自旋锁了,会造成长时间的cpu占用导致cpu资源使用的浪费。
可重入锁
可重入锁reentrant是指自身线程在重新获取锁的时候不会阻塞自己,例如在递归操作中重新获取锁。
公平锁,非公平锁
多个线程竞争同一把锁,锁在释放的时候线程可以按照竞争顺序获得锁就是公平锁,反正随机获得就是非公平锁。一般情况下,非公平锁的吞吐量比公平锁要大,在没有特殊要求下一般使用非公平锁。
可中断锁
竞争锁的线程向已获取锁的线程请求其中断,让出锁。持有锁的线程可根据自身情况响应或者忽略。