Java锁的分类概念和状态
Posted little lunatic
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java锁的分类概念和状态相关的知识,希望对你有一定的参考价值。
在Java中,synchronized和Lock的实现类锁都是悲观锁。
一、悲观锁和乐观锁
悲观锁:获取数据时会先加锁,确保数据不会被其他线程修改。悲观锁适合写操作多的场景。
乐观锁:认为自己在使用数据时,不会有别的线程来修改数据,更新数据前会判断有没有别的线程更新了这个数据。乐观锁适合读操作多的场景。
乐观锁在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锁的分类概念和状态的主要内容,如果未能解决你的问题,请参考以下文章