java中各种锁介绍
1.公平锁 / 非公平锁
公平锁
在java代码中类名是FairSync。
公平锁是指多个线程按照申请锁的顺序来获取锁。
非公平锁
在java代码中类名是NonfairSync。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,因此也会造成优先级反转或者饥饿现象(一直等待)。
对于Java ReentrantLock(参见2 可重入锁)而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大(因为如果发出请求的同时该锁变成可用状态,那么这个线程会跳过队列中所有的等待线程而获得锁,跳过队列和不用唤醒之前的睡眠线程使得效率提高)
对于Synchronized而言,也是一种非公平锁。由于其原理是通过虚拟机层面保证的(方法区关键词声明ACC_SYNCHRONIZED或者monitorenter、monitorexit指令),并不像ReentrantLock是通过AQS(AbstractQueuedSynchronizer,本质上是个双向队列)的来实现线程调度,所以并没有任何办法使其变成公平锁。
因此我们看到因为不需要AQS实现,非公平锁相对于公平锁是更基本更原子性的存在,也可以理解为公平锁是一种特殊的非公平锁。
2.可重入锁 / 不可重入锁
可重入锁又名递归锁,是指线程可以进入任何一个它已经拥有的锁所同步着的代码块,即在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。
对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。
对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。
3.独享锁 / 共享锁
独享锁是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。
对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
对于Synchronized而言,当然是独享锁。
4.互斥锁 / 读写锁
其实就是独享锁、共享锁的另一种说法;互斥锁实质就是 ReentrantLock,读写锁实质就是 ReadWriteLock。
5.乐观锁 / 悲观锁
乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度。在Java和数据库中都有此概念对应的实际应用。
先说概念。对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。
而乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法, Java中ReentrantLock、ReentrantReadWriteLock都是基于cas算法,原子类中的递增操作也是通过CAS自旋实现的。
6.分段锁
实质是一种锁的设计策略,不是具体的锁,对于 ConcurrentHashMap 而言其并发的实现就是通过分段锁的形式来实现高效并发操作;当要 put 元素时并不是对整个 hashmap 加锁,而是先通过 hashcode 知道它要放在哪个分段,然后对分段进行加锁,所以多线程 put 元素时只要放在的不是同一个分段就做到了真正的并行插入,但是统计 size 时就需要获取所有的分段锁才能统计;分段锁的设计是为了细化锁的粒度。
7.偏向锁 / 轻量级锁 / 重量级锁
这种分类是按照锁状态来归纳的,并且是针对 synchronized 的,java 1.6 为了减少获取锁和释放锁带来的性能问题而引入的一种状态,其状态会随着竞争情况逐渐升级,小标题的顺序就是锁升级的顺序。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后无法降为偏向锁,这种升级无法降级的策略目的就是为了提高获得锁和释放锁的效率。
8 什么是偏向锁
偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。 偏向锁会在对象内存的对象头的32位的markwork结构有锁标记01,并且记录该唯一竞争线程的id。
9 什么是轻量级锁,偏向锁如何转化轻量级锁
如果在运行过程中,遇到了其他线程(id不一样)抢占锁,则持有偏向锁的线程会在适当的时候被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁,锁标记00。这时对内存数据的主要操作是复制锁对象的markwork到线程自己创建的锁记录空间储存,并且两者储存有互通的指针。
轻量级锁主要有两个,自旋锁和自适应自旋锁。
10.自旋锁
通过不断的轮询来尝试获取锁,是一种占用CPU时间的非阻塞锁,当锁的等待时间短,效率会很高。与阻塞锁相比,主要是避免了 阻塞过程用户态和内核态切换的开销。
11 自适应自旋锁
与自旋锁的区别相比是根据之前的自旋锁的结果自适应当前锁的自旋,如果之前成功了就继续自旋,如果之前失败则省去自旋直接阻塞当前线程。
12 重量级锁
也就是java 1.6之前的锁,主要是之前非公平锁所说的监视器(虚拟机monitorenter、monitorexit指令)和关键词ACC_SYNCHRONIZED修饰实现。
讲完了重量级锁,锁升级整个过程简略图就可以做个简略概括了,如下所述: