Java锁的分类概念和状态

Posted little lunatic

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java锁的分类概念和状态相关的知识,希望对你有一定的参考价值。

在Java中,synchronizedLock的实现类锁都是悲观锁。

一、悲观锁和乐观锁

悲观锁:获取数据时会先加锁,确保数据不会被其他线程修改。悲观锁适合写操作多的场景。

乐观锁:认为自己在使用数据时,不会有别的线程来修改数据,更新数据前会判断有没有别的线程更新了这个数据。乐观锁适合读操作多的场景。

乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

二、自旋锁

实现原理:CAS算法

阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。

但是,自旋不能代替阻塞!如果锁被占用的时间太长,自旋的线程就会浪费CPU资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。

三、无锁,偏向锁,轻量级锁,重量级锁

这四种是锁的状态,针对的是synchronized。

Synchronized能实现线程同步需要了解两个重要的概念Java对象头,Monitor。synchronized会对同步资源加锁,这把锁就存在Java对象头。

Java对象头包括两部分:Mark Word(标记字段)、Klass Pointer(类型指针)。Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。

Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。

Monitor 是线程私有的数据结构,每个线程都有一个可用的monitor record列表和一个全局列表。每个被锁的对象都会和一个monitor关联,monitor中的owner字段存放拥有该锁的线程唯一标识,表示该锁被这个线程占用。synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的Mutex Lock(一种互斥锁)来实现的线程同步。这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。

无锁

所有线程都能访问并修改同步资源,但是只有一个线程能修改成功。

特点:修改操作在循环内进行,线程会不断的尝试修改共享资源。

偏向锁

偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价,提高性能。

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。

偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。

轻量级锁

当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁。其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。

若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。

轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能

重量级锁

升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。

重量级锁是将除了拥有锁的线程以外的线程都阻塞。

四、公平锁和非公平锁

公平锁:按照申请锁的顺序来获取锁,线程直接进入队列中排队。

优点:等待锁的线程不会饿死。

缺点:整体吞吐量比不上非公平锁,除队列第一个线程外,其他线程都会阻塞。

非公平锁:多个线程加锁时,直接获取锁(插队),如果成功,这个线程就不用阻塞直接就获取到锁,失败才会进入等待队列队尾等待。

优点:减少CPU唤醒线程的开销。吞吐量大。

缺点:处于等待队列的线程有可能饿死,或者等很久才会获取到锁。

五、可重入锁和非可重入锁

可重入锁

又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁。

优点:可一定程度避免死锁。

非可重入锁

在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),会因为之前已经获取过还没释放而阻塞。

六、共享锁和独占锁

共享锁

该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁(独享锁)。获得共享锁的线程只能读数据,不能修改数据。

独占锁

又叫排他锁,该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK 中的 synchronized 和 JUC 中 Lock 的实现类就是互斥锁。

知识来源:Java锁的概念|不可不说的Java“锁”事

以上是关于Java锁的分类概念和状态的主要内容,如果未能解决你的问题,请参考以下文章

Java中的锁

java中锁的概念/介绍

java多线程,对象锁是啥概念?

Java Review - 并发编程_锁的分类

Java Review - 并发编程_锁的分类

锁的分类以及相关讲解