Java锁的种类以及辨析 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类型却很少被提及。本系列文章将分析JAVA中常见的锁以及其特性,为大家答疑解惑。

1、自旋锁

2、自旋锁的其他种类

3、阻塞锁

4、可重入锁

5、读写锁

6、互斥锁

7、悲观锁

8、乐观锁

9、公平锁

10、非公平锁

11、偏向锁

12、对象锁

13、线程锁

14、锁粗化

15、轻量级锁

16、锁消除

17、锁膨胀

18、信号量

背景

锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类型却很少被提及。

自旋锁

自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被当前线程改变时其他前程才能进入临界区。

自旋锁流程:获取自旋锁时,如果没有任何线程保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。

简单实现原理的代码如下:

/*** 自旋锁原理简单示例
*/
public class SpinLock {private AtomicReference<Thread> sign = new AtomicReference<>();// 获取锁public void lock() {Thread current = Thread.currentThread();while (!sign.compareAndSet(null, current)) {}}// 释放锁public void unlock() {Thread current = Thread.currentThread();sign.compareAndSet(current, null);}
}

要理解以上代码,我们要先弄清楚AtomicReference的作用。

AtomicReference:位于java.util.concurrent.atomic包下。从包名就可知道它的大致作用:在并发环境中保证引用对象的原子操作。

查看AtomicReference源码:

package java.util.concurrent.atomic;
import java.util.function.UnaryOperator;
import java.util.function.BinaryOperator;
import sun.misc.Unsafe;/*** An object reference that may be updated atomically. See the {@link* java.util.concurrent.atomic} package specification for description* of the properties of atomic variables.* @since 1.5* @author Doug Lea* @param <V> The type of object referred to by this reference*/
public class AtomicReference<V> implements java.io.Serializable {private static final long serialVersionUID = -1848883965231344442L;private static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile V value;...(省略)/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.* @param expect the expected value* @param update the new value* @return {@code true} if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(V expect, V update) {return unsafe.compareAndSwapObject(this, valueOffset, expect, update);}...(省略)

发现AtomicReference实现的基本原理是使用volatile关键字和Unsafe类来保证其可见性和原子性。(PS:在此暂不作扩展阅读Unsafe类)

我们重点关注AtomicReference.compareAndSet()这个自旋锁用到的方法。从方法注释和方式实现,可以理解:这个方法的意思就是当当前的值==(注意是双等号)期望的值(即传入的第一个参数)时,把当前值更新为新值(即传入的第二个参数),并且返回true,否则返回false。

再回过头,看之前自旋锁的代码,就很好理解了。一开始AtomicReference中的值为null,当有线程获得锁后,将值更新为该线程。当其他线程进入被锁的方法时,由于sign.compareAndSet(null, current)始终返回的是false,导致while循环体一直在运行,知道获得锁的线程调用unlock方法,将当前持有线程重新设置为null:sign.compareAndSet(current, null)其他线程才可获得锁。

阻塞锁

阻塞锁,与自旋锁不同,改变了线程的运行状态。阻塞锁,可以说是让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。

阻塞锁和自旋锁最大的区别就在于,当获取锁是,如果锁有持有者,当前线程是进入阻塞状态,等待当前线程结束而被唤醒的。

简单实现原理的代码如下:

/*** 阻塞锁原理简单示例** @author zacard* @since 2016-01-13 22:02*/
public class BlockLock {private AtomicReference<Thread> sign = new AtomicReference<>();// 获取锁public void lock() {Thread current = Thread.currentThread();if (!sign.compareAndSet(null, current)) {LockSupport.park();}}// 释放锁public void unlock() {Thread current = Thread.currentThread();sign.compareAndSet(null, current);LockSupport.unpark(current);}
}

要理解以上代码,我们要先弄清楚LockSupport的作用。

LockSupport:位于java.util.concurrent.locks包下(又是j.u.c)。同样,从包名和类名即可知道其作用:提供并发编程中的锁支持。

还是先查看下LockSupport的源码:

public class LockSupport {private LockSupport() {} // Cannot be instantiated.private static void setBlocker(Thread t, Object arg) {// Even though volatile, hotspot doesn't need a write barrier here.UNSAFE.putObject(t, parkBlockerOffset, arg);}...(省略)

又是sun.misc.Unsafe这个类,在此我们不得不先扩展研究下这个Unsafe类的作用和原理了。

sun.misc.Unsafe:有个称号叫做魔术类。因为他能直接操作内存等一些复杂操作。包括直接修改内存值,绕过构造器,直接调用类方法等。当然,他主要提供了CAS(compareAndSwap)原子操作而被我们熟知。

查看Unsafe类源码:

public final class Unsafe {private static final Unsafe theUnsafe;...(省略)private Unsafe() {}@CallerSensitivepublic static Unsafe getUnsafe() {Class var0 = Reflection.getCallerClass();if(!VM.isSystemDomainLoader(var0.getClassLoader())) {throw new SecurityException("Unsafe");} else {return theUnsafe;}}...(省略)

根据代码可知:Unsafe是final类,意味着我们不能通过继承来使用或改变这个类的方法。然后构造器是私有的,也不能实例化。但是他自己保存了一个静态私有不可改变的实例“theUnsafe”,并且只提供了一个静态方法getUnsafe()来获取这个类的实例。

但是这个getUnsafe方法确有个限制:注意if语句里的判断,他表示如果不是受信任的类调用,会直接抛出异常。显然,我们平常编写的类都是不受信任的!

但是,我们有反射!既然他已经持有了一个实例,就能通过反射强行窃取这个私有的实例。

代码如下:

public void getUnsafe() {try {Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);Unsafe unsafe = (Unsafe) field.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();}
}

Unsafe类的方法基本都是native关键字修饰的,也就是说这些方法都是原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。这也就是为什么Unsafe能够直接操作内存等一些特权功能的原因。

回过头看下LockSupport中park()和uppark()这2个方法的作用。

LockSupport.unpark():

/*** Makes available the permit for the given thread, if it* was not already available.  If the thread was blocked on* {@code park} then it will unblock.  Otherwise, its next call* to {@code park} is guaranteed not to block. This operation* is not guaranteed to have any effect at all if the given* thread has not been started.** @param thread the thread to unpark, or {@code null}, in which case*        this operation has no effect*/
public static void unpark(Thread thread) {if (thread != null)UNSAFE.unpark(thread);
}

根据方法注释:对于给定线程,将许可证设置为可用状态。如果这个线程是因为调用park()而处于阻塞状态,则清除阻塞状态。反之,这个线程在下次调用park()时,将保证不被阻塞。

LockSupport.park():

/*** Disables the current thread for thread scheduling purposes unless the* permit is available.** <p>If the permit is available then it is consumed and the call returns* immediately; otherwise* the current thread becomes disabled for thread scheduling* purposes and lies dormant until one of three things happens:** <ul>* <li>Some other thread invokes {@link #unpark unpark} with the* current thread as the target; or** <li>Some other thread {@linkplain Thread#interrupt interrupts}* the current thread; or** <li>The call spuriously (that is, for no reason) returns.* </ul>** <p>This method does <em>not</em> report which of these caused the* method to return. Callers should re-check the conditions which caused* the thread to park in the first place. Callers may also determine,* for example, the interrupt status of the thread upon return.** @param blocker the synchronization object responsible for this*        thread parking* @since 1.6*/
public static void park(Object blocker) {Thread t = Thread.currentThread();setBlocker(t, blocker);UNSAFE.park(false, 0L);setBlocker(t, null);
}

根据注释:除非许可证是可用的,不然将当前线程的调度设置为不可用。当许可是可用时,方法会立即返回,不会阻塞,反之就会阻塞当前线程直到下面3件事发生:

o 其他线程调用了unpark(此线程)

o 其他线程interrupts(终止)了此线程

o 调用时发生未知原因的返回

重入锁

重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。在JAVA环境下ReentrantLock和synchronized都是重入锁。

测试代码如下:

/*** 测试ReentrantLock和synchronized*/
@Test
public void testReentrantLock() {// ReentrantLock testfor (int i = 0; i < 3; i++) {new Thread(new Runnable() {ReentrantLock lock = new ReentrantLock();public void get() {lock.lock();System.out.println("ReentrantLock:" + Thread.currentThread().getId());set();lock.unlock();}public void set() {lock.lock();System.out.println("ReentrantLock:" + Thread.currentThread().getId());lock.unlock();}@Overridepublic void run() {get();}}).start();}// synchronized testfor (int i = 0; i < 3; i++) {new Thread(new Runnable() {public synchronized void get() {System.out.println("synchronized:" + Thread.currentThread().getId());set();}public synchronized void set() {System.out.println("synchronized:" + Thread.currentThread().getId());}@Overridepublic void run() {get();}}).start();}
}

2段代码的输出一致:都会重复输出当前线程id2次。

可重入锁最大的作用是避免死锁。以自旋锁作为例子:

/*** 自旋锁原理简单示例** @author zacard* @since 2016-01-13 21:40*/
public class SpinLock {private AtomicReference<Thread> sign = new AtomicReference<>();// 获取锁public void lock() {Thread current = Thread.currentThread();while (!sign.compareAndSet(null, current)) {}}// 释放锁public void unlock() {Thread current = Thread.currentThread();sign.compareAndSet(current, null);}
}

o 若有同一线程两调用lock(),会导致第二次调用lock位置进行自旋,产生了死锁说明这个锁并不是可重入的。(在lock函数内,应验证线程是否为已经获得锁的线程)

o 若1问题已经解决,当unlock()第一次调用时,就已经将锁释放了。实际上不应释放锁

自旋锁避免死锁的方法(采用计数次统计):

/*** 自旋锁改进** @author Guoqw* @since 2016-01-14 14:11*/
public class SpinLockImprove {private AtomicReference<Thread> owner = new AtomicReference<>();private int count = 0;/*** 获取锁*/public void lock() {Thread current = Thread.currentThread();if (current == owner.get()) {count++;return;}while (!owner.compareAndSet(null, current)) {}}/*** 释放锁*/public void unlock() {Thread current = Thread.currentThread();if (current == owner.get()) {if (count != 0) {count--;} else {owner.compareAndSet(current, null);}}}
}

改进后自旋锁即为重入锁的简单实现。

java锁的种类及研究相关推荐

  1. java锁的种类以及辨析(转载)

    java锁的种类以及辨析(一):自旋锁 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我 ...

  2. 介绍了Oracle数据库锁的种类及研究

    http://www.dedecms.com/web-art/shujuku/Oracle/20061008/37324.html 介绍了Oracle数据库锁的种类及研究 来源:ZDNET 作者:佚名 ...

  3. java锁的种类以及辨析(一):自旋锁

    java锁的种类以及辨析(一):自旋锁 1.自旋锁 自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区.如下 public class SpinLock ...

  4. Java锁的种类以及辨析(四):可重入锁

    Java锁的种类以及辨析(四):可重入锁 本文里面讲的是广义上的可重入锁,而不是单指JAVA下的ReentrantLock. 可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数 ...

  5. Java锁的种类以及辨析(二):自旋锁的其他种类

    作者:山鸡 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我们开发提供了便利,但是锁的具 ...

  6. Java锁的种类以及辨析

    锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类 ...

  7. Java锁的种类和区别

    在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级 ...

  8. Oracle数据库锁的种类及研究

    数据库是一个多用户使用的共享资源.当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况.若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性. 在数据库中有两 ...

  9. 【躲不过的Java “锁事”】一文扫除对Java各种锁的困扰!

    简介 锁是Java中快捷理解多线程的一条捷径,为我们开发多线程提供理论支持,想成为一个合格的工程师,Java中的锁是必定躲不过的一个知识点,并且内容繁多,本文点到为止,浅谈Java中的各种锁,带您理解 ...

最新文章

  1. IDEA打包出现Unable to find main class
  2. 2020年的AI现状
  3. 安装clangd:‘GLIBC_2.18‘ not found解决
  4. Java 配置C3P0数据连接池存入数据存入数据库出现中文乱码问题
  5. Cocos2d-x--Box2D绘制出两个矩形框的解决方案
  6. 【翻译】GitHub Pages Basics 基本使用帮助【一】GitHub Pages 是什么?
  7. 链表创建为什么需要使用内存分配?
  8. BZOJ3240 NOI2013矩阵游戏(数论)
  9. yiibooster+bsie
  10. 一个漂亮的电子数字字体分享electronicFont
  11. 5GgNB和ng-eNB的主要功能
  12. android 拼图游戏2(可从手机选择任意一张图片)
  13. 基于Python的信用评分卡建模分析
  14. u盘提示格式化怎么修复教程
  15. linux下同一个tomcat部署多个项目 同一个端口
  16. IntelliJ IDEA快速入门 | 第三十篇:如何来自定义模板呢?
  17. 网络命令一览表(绝对实用)
  18. js中的this及箭头函数
  19. C# WPF框架Caliburn.Micro快速搭建
  20. “金字塔原理”-写作

热门文章

  1. 用电脑无线投屏到电视屏幕的连接方法
  2. 用图章工具 修改数字
  3. Unity 粒子 基础
  4. Python+selenium点击网页上指定坐标
  5. JavaWeb (SSM框架)
  6. JavaGUI——Java图形用户界面
  7. 宽带提速后如何恰如其分的选择路由器?
  8. Word突然不能复制粘贴怎么办?
  9. 网络设置巨形帧_NAS的巨型帧(Jumbo_Frame)设置对其传输速度的影响的评测与分析...
  10. 关于nvidia-smi和nvcc -V显示的cuda版本不一致的问题