文章目录
Java 中所有的锁
悲观锁与乐观锁
悲观锁
悲观锁,顾名思义,认为一定有其他线程操作共享资源,一定在要获取数据前加锁,保证只有一个线程在操作同步资源,sychronized 关键字和 lock 实现类都是属于悲观锁,使用场景:主要是在多线程写入或者修改数据的情况下使用,保证数据的安全性。
乐观锁
乐观锁,顾名思义,认为没有其他线程在操作同步资源,在修改同步资源时再判断是否有其他线程修改过同步资源。没有的话直接修改,有的话就采用其他措施不如报错或者重试。在Java中,原子类的自增就是一种乐观锁,使用的是 CAS 自旋完成的。
自旋锁与适应性自旋锁
自旋锁
非自旋锁平时在使用锁的时候,线程如果没有获取到锁,就会使用 CPU 来切换线程状态,这一操作是比较耗时的,如果用户的同步代码较少或较为简单,CPU 切换状态的时间会比执行同步代码的时间高。浪费 CPU 的资源。
自旋锁在尝试获取锁失败后,线程不会阻塞,通过自旋,自旋完成后继续尝试获取锁,等待锁的释放。可以减少 CPU 进行线程状态的次数。但是如果自旋时间太久的话,因为线程是一直占用 CPU 时间的,也会造成 CPU 浪费。
无锁,偏向锁,轻量级锁,重量级锁
轻量级锁与重量级锁
重量级锁就是除了获取锁的线程以外所有线程都进入阻塞状态
轻量级锁就是没有获取到锁的线程可以通过自旋的方式,自旋后可以继续尝试获取锁。减少 CPU 切换线程状态的次数。
公平锁与非公平锁
公平锁
线程到了之后会在队列中排队获取锁,线程获取锁的是优点是:线程不会饿死。缺点是 CPU 的吞吐效率不高。
非公平锁
线程到了之后先尝试获取锁,获取不到再排队,优点是:提高了 CPU 的吞吐效率,减少了 CPU 切换线程状态的次数。缺点:线程可能饿死。
非公平锁的效率比较快,因为如果是公平锁,每一次获取锁就要唤醒一个线程,CPU 切换线程状态是比较耗时的,会消耗 CPU 的资源
非公平锁,线程一到先获取锁,而不是先堵塞,这样有几率减少 CPU 的切换线程状态的次数,减少 CPU 的压力。
可重入锁与不可重入锁
可重入锁
在外层获取锁的线程,该县城在内层中还可以继续获取相同对象的锁(必须是相同对象的锁或者.class)。sychronized 关键字与 ReentrantLock 属于可重入锁。
不可重入锁
在外层获取锁的线程,不可在内层中继续获取相同对象的锁,比如 NonReentrantLock 是不可重入锁。
ReentrantLock 与 NonReentrantLock,两者都是继承 AQS 父类,AQS 维护一个 state 状态值,当ReentrantLock 在内部继续尝试获取锁的时候,state 值+1,当 ReentrantLock 释放锁的时候 state - 1,只有 state 值为 0 的时候才会真正的释放锁。
NonReentrantLock 不可重入锁,在释放锁的时候会直接放 state 设置为 0 并释放锁,当 state != 0 时,不可以获取锁。
独享锁与共享锁
独享锁
又名排他锁,互斥锁,只有一个线程可以获取,一般为写锁,当有一个线程获取到独享锁,其他线程都不可以获取锁,获取锁的线程可以写入数据,修改数据等等,直到该线程释放,其余线程才可以获取锁。sychronized 与 ReentrantLock 都是独享锁。
共享锁
又名读锁,当一个线程获取到该共享锁时,其他线程也可以获取共享锁,但是不能获取其他的锁,多个线程可以获取,ReentrantReadWriteLock 中的读锁就是共享锁, 也一般为读锁。
ReenTrantReaderWriteLock 中的 ReadLock 是独享锁,WriteLock 是共享锁。