各类锁(互斥锁,自旋锁,读写锁,乐观锁,悲观锁,死锁)
互斥锁
当有一个线程要访问共享资源(临界资源)之前会对线程访问的这段代码(临界区)进行加锁。如果在加锁之后没释放锁之前其他线程要对临界资源进行访问,则这些线程会被阻塞睡眠,直到解锁,如果解锁时有一个或者多个线程阻塞,那么这些锁上的线程就会变成就绪状态,然后第一个变为就绪状态的线程就会获取资源的使用权,并且再次加锁,其他线程继续阻塞等待。
读写锁
也叫做共享互斥锁,读模式共享,写模式互斥。有点像数据库负载均衡的读写分离模式。它有三种模式:读加锁状态,写加锁状态和不加锁状态。简单来说就是只有一个线程可以占有写模式的读写锁,但是可以有多个线程占用读模式的读写锁。
当写加锁的模式下,任何线程对其进行加锁操作都会被阻塞,直到解锁。
当在读加锁的模式下,任何线程都可以对其进行读加锁的操作,但所有试图进行写加锁操作的线程都会被阻塞。直到所有读线程解锁。但是当读线程太多时,写线程一直被阻塞显然是不对的,所以一个线程想要对其进行写加锁时,就会阻塞读加锁,先让写加锁线程加锁
自旋锁
自旋锁和互斥锁很像,唯一不同的是自旋锁访问加锁资源时,会一直循环的查看是否释放锁。这样要比互斥锁效率高很多,但是只会占用CPU。所以自旋锁适用于多核的CPU。但是还有一个问题是当自旋锁递归调用的时候会造成死锁现象。所以慎重使用自旋锁。
乐观锁
这其实是一种思想,当线程去拿数据的时候,认为别的线程不会修改数据,就不上锁,但是在更新数据的时候会去判断以下其他线程是否修改了数据。通过版本来判断,如果数据被修改了就拒绝更新,之所以叫乐观锁是因为并没有加锁。
乐观锁的实现机制CAS
CAS理解类似于事务:全称Compare And Swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制。
处理器 CAS 指令(内存地址V, 旧值, 新值)
CAS的原理:
利用现代处理器都支持的CAS的指令,循环这个指令,直到成功为止.
CAS原理流程图如下:
CAS相关的问题:
- ABA问题:在乐观锁期间,另一个线程修改old value值为new value值,然后又把new value值重新修改为old value.
- ABA解决方案:加版本号
- AtomicMarkableReference 可以解决,使用boolean变量——表示引用变量是否被更改过,不关心中间变量变化了几次
- AtomicStampedReference 也可以解决,其中的构造方法中initialStamp(时间戳)用来唯一标识引用变量,引用变量中途被更改了几次
- ABA解决方案:加版本号
- 开销问题:CAS在一直拿不到锁时会一直循环,耗费CPU,造成资源上的浪费
- 只能保证一个共享变量的原子操作:在内存中 get() 变量时,只能get出一个变量
- 取巧解决方案:把两个变量封装成一个
悲观锁
当线程去拿数据的时候,总以为别的线程会去修改数据,所以它每次拿数据的时候都会上锁,别的线程去拿数据的时候就会阻塞。
这两种锁一般用于数据库,当一个数据库的读操作远远大于写的操作次数时,使用乐观锁会加大数据库的吞吐量。