同步互斥
锁
-
乐观锁和悲观锁
区分这两种锁其实要站在应用的角度。 -
乐观锁
乐观锁乐观的认为出现竞争的场景是非常少的,不需要在资源获取的时候加锁,只需要在事务提交的时候通过CAS的方式:判断当前版本是否和自己之前读取的一致,一致的话更新,不一致的话回滚后再重新执行。整个过程阻塞在提交过程中,事务的执行不阻塞。 -
悲观锁
悲观锁悲观的认为出现竞争的场合是非常多的,需要在获取资源的时候加上锁。这时,事务的执行是被阻塞的。 -
乐观锁和悲观锁使用场景
乐观锁被用在读比写多得多的场合,或者事务之间的关联度比较低,如果存在很多写事务,写事务与写事务之间、写事务与读事务之间也较少存在资源的竞争。悲观锁被用在写较多且事务之间关联比较大的场景。 -
自旋锁
采用 CAS 的方式,忙等,适合临界区耗时较少、需要频繁进入的场景,在这种场合下线程的切换可能会导致更大的开销。 -
互斥锁
多线程出现竞争时,只有一个线程能够进入临界区,这时其它线程阻塞而不是占用CPU忙等。当锁被释放后,操作系统会唤醒等待锁的线程 -
可重入锁
本线程可重入,在递归调用等场景下避免死锁。一般可重入锁能满足大部分场景的需求。 -
读写锁
配套使用,读锁是共享锁,写锁是独占锁。读写锁的逻辑是悲观的(等待机制)。 -
公平锁和非公平锁
许多线程被阻塞在同一个临界区,这时候如果临界区被释放,对于公平锁:所有等待的线程先来后到;对于非公平锁,优先级高的线程先获得锁。 -
可中断锁和不可中断锁
当出现某种异常情况时,线程可能一直阻塞在锁获取阶段,如果这个锁是可中断锁,那么其它线程或者线程自己可以去中断这个过程,否则不行。 -
Java synchronized 锁升级:偏向锁-->自旋锁-->互斥锁
在只有一个线程使用临界区时,为提高效率线程不释放锁,如果有其他线程来征用资源,那么锁会升级成自旋锁,当出现长时间的忙等时,锁会再升级成互斥锁。
信号量
常用于生产者消费者模型
条件变量
线程阻塞(睡眠)等待条件达成,但是由于事务处理需要手动完成,操作系统无法代劳,所以会有相应的signal函数作配合。条件变量的使用需要传入两个参数,一个是等待的变量 cond,另一个是锁。
- 为什么需要传入锁呢?
首先由于需要一个共享变量来进行通讯(什么情况下才需要睡眠,得有一个变量来作判断),所以需要锁来保护这个变量。然后,线程睡眠之后必须保证这个锁是被释放的,但是被wakeup之后又要立刻获得这个锁,第一个要求是为了避免死锁,第二个要求是为了保证临界区的原子性。解决方案是传入这个锁给操作系统,操作系统在内部借助进程锁来做到这两点。
睡眠锁(MIT6.S081)、互斥锁、条件变量
睡眠锁和互斥锁其实是一个概念,用户只需要关注加锁和解锁,操作系统会负责阻塞的处理。条件变量和前两个的区别在于,条件变量增加了灵活性,相当于把互斥锁的锁保护提到了外面,用户得以拓展临界区(增加一些自己的操作)。并且这个提到外面的锁是互斥锁,而不是互斥锁实现中用到的自旋锁,所以在技术实现上,条件变量相对互斥锁而言,并不简单一些。