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

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

1、自旋锁

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

01 public class SpinLock {
02  
03   private AtomicReference<Thread> sign =newAtomicReference<>();
04  
05   public void lock(){
06     Thread current = Thread.currentThread();
07     while(!sign .compareAndSet(null, current)){
08     }
09   }
10  
11   public void unlock (){
12     Thread current = Thread.currentThread();
13     sign .compareAndSet(current, null);
14   }
15 }

使用了CAS原子操作,lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。

当有第二个线程调用lock操作时由于owner值不为空,导致循环一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。

由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

注:该例子为非公平锁,获得锁的先后顺序,不会按照进入lock的先后顺序进行。

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

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

2.自旋锁的其他种类

上篇我们讲到了自旋锁,在自旋锁中 另有三种常见的锁形式:TicketLock ,CLHlock 和MCSlock

Ticket锁主要解决的是访问顺序的问题,主要的问题是在多核cpu上

01 package com.alipay.titan.dcc.dal.entity;
02  
03 import java.util.concurrent.atomic.AtomicInteger;
04  
05 public class TicketLock {
06     private AtomicInteger                     serviceNum = new AtomicInteger();
07     private AtomicInteger                     ticketNum  = new AtomicInteger();
08     private static final ThreadLocal<Integer> LOCAL      = new ThreadLocal<Integer>();
09  
10     public void lock() {
11         int myticket = ticketNum.getAndIncrement();
12         LOCAL.set(myticket);
13         while (myticket != serviceNum.get()) {
14         }
15  
16     }
17  
18     public void unlock() {
19         int myticket = LOCAL.get();
20         serviceNum.compareAndSet(myticket, myticket + 1);
21     }
22 }

每次都要查询一个serviceNum 服务号,影响性能(必须要到主内存读取,并阻止其他cpu修改)。

CLHLock 和MCSLock 则是两种类型相似的公平锁,采用链表的形式进行排序,

01 importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;
02  
03 public class CLHLock {
04     public static class CLHNode {
05         private volatile boolean isLocked = true;
06     }
07  
08     @SuppressWarnings("unused")
09     private volatileCLHNode                                           tail;
10     private static finalThreadLocal<CLHNode>                          LOCAL   = new ThreadLocal<CLHNode>();
11     private static finalAtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,
12                                                                                    CLHNode.class,"tail");
13  
14     public void lock() {
15         CLHNode node = new CLHNode();
16         LOCAL.set(node);
17         CLHNode preNode = UPDATER.getAndSet(this, node);
18         if (preNode != null) {
19             while (preNode.isLocked) {
20             }
21             preNode = null;
22             LOCAL.set(node);
23         }
24     }
25  
26     public void unlock() {
27         CLHNode node = LOCAL.get();
28         if (!UPDATER.compareAndSet(this, node,null)) {
29             node.isLocked = false;
30         }
31         node = null;
32     }
33 }

CLHlock是不停的查询前驱变量, 导致不适合在NUMA 架构下使用(在这种结构下,每个线程分布在不同的物理内存区域)

MCSLock则是对本地变量的节点进行循环。不存在CLHlock 的问题。

01 importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;
02  
03 public class MCSLock {
04     public static class MCSNode {
05         volatile MCSNode next;
06         volatile boolean isLocked = true;
07     }
08  
09     private static finalThreadLocal<MCSNode>                          NODE    = new ThreadLocal<MCSNode>();
10     @SuppressWarnings("unused")
11     private volatileMCSNode                                           queue;
12     private static finalAtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,
13                                                                                    MCSNode.class,"queue");
14  
15     public void lock() {
16         MCSNode currentNode = new MCSNode();
17         NODE.set(currentNode);
18         MCSNode preNode = UPDATER.getAndSet(this, currentNode);
19         if (preNode != null) {
20             preNode.next = currentNode;
21             while (currentNode.isLocked) {
22  
23             }
24         }
25     }
26  
27     public void unlock() {
28         MCSNode currentNode = NODE.get();
29         if (currentNode.next == null) {
30             if (UPDATER.compareAndSet(this, currentNode, null)) {
31  
32             } else {
33                 while (currentNode.next == null) {
34                 }
35             }
36         } else {
37             currentNode.next.isLocked = false;
38             currentNode.next = null;
39         }
40     }
41 }

从代码上 看,CLH 要比 MCS 更简单,

CLH 的队列是隐式的队列,没有真实的后继结点属性。

MCS 的队列是显式的队列,有真实的后继结点属性。

JUC ReentrantLock 默认内部使用的锁 即是 CLH锁(有很多改进的地方,将自旋锁换成了阻塞锁等等)。

Java锁的种类以及辨析(三):阻塞锁

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

三、阻塞锁:

阻塞锁,与自旋锁不同,改变了线程的运行状态。
在JAVA环境中,线程Thread有如下几个状态:

1,新建状态

2,就绪状态

3,运行状态

4,阻塞状态

5,死亡状态

阻塞锁,可以说是让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。
JAVA中,能够进入\退出、阻塞状态或包含阻塞锁的方法有 ,synchronized 关键字(其中的重量锁),ReentrantLock,Object.wait()\notify(),LockSupport.park()/unpart()(j.u.c经常使用)

下面是一个JAVA 阻塞锁实例

01 package lock;
02  
03 importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;
04 import java.util.concurrent.locks.LockSupport;
05  
06 public class CLHLock1 {
07     public static class CLHNode {
08         private volatile Thread isLocked;
09     }
10  
11     @SuppressWarnings("unused")
12     private volatileCLHNode                                            tail;
13     private static finalThreadLocal<CLHNode>                           LOCAL   = new ThreadLocal<CLHNode>();
14     private static finalAtomicReferenceFieldUpdater<CLHLock1, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock1.class,
15                                                                                     CLHNode.class,"tail");
16  
17     public void lock() {
18         CLHNode node = new CLHNode();
19         LOCAL.set(node);
20         CLHNode preNode = UPDATER.getAndSet(this, node);
21         if (preNode != null) {
22             preNode.isLocked = Thread.currentThread();
23             LockSupport.park(this);
24             preNode = null;
25             LOCAL.set(node);
26         }
27     }
28  
29     public void unlock() {
30         CLHNode node = LOCAL.get();
31         if (!UPDATER.compareAndSet(this, node,null)) {
32             System.out.println("unlock\t" + node.isLocked.getName());
33             LockSupport.unpark(node.isLocked);
34         }
35         node = null;
36     }
37 }

在这里我们使用了LockSupport.unpark()的阻塞锁。 该例子是将CLH锁修改而成。

阻塞锁的优势在于,阻塞的线程不会占用cpu时间, 不会导致 CPu占用率过高,但进入时间以及恢复时间都要比自旋锁略慢。

在竞争激烈的情况下 阻塞锁的性能要明显高于 自旋锁。

理想的情况则是; 在线程竞争不激烈的情况下,使用自旋锁,竞争激烈的情况下使用,阻塞锁。

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

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

四、可重入锁:

本文里面讲的是广义上的可重入锁,而不是单指JAVA下的ReentrantLock。

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

下面是使用实例

01 public class Test implements Runnable{
02  
03     public synchronized void get(){
04         System.out.println(Thread.currentThread().getId());
05         set();
06     }
07  
08     public synchronized void set(){
09         System.out.println(Thread.currentThread().getId());
10     }
11  
12     @Override
13     public void run() {
14         get();
15     }
16     public static void main(String[] args) {
17         Test ss=new Test();
18         new Thread(ss).start();
19         new Thread(ss).start();
20         new Thread(ss).start();
21     }
22 }
01 public class Test implements Runnable {
02     ReentrantLock lock = new ReentrantLock();
03  
04     public void get() {
05         lock.lock();
06         System.out.println(Thread.currentThread().getId());
07         set();
08         lock.unlock();
09     }
10  
11     public void set() {
12         lock.lock();
13         System.out.println(Thread.currentThread().getId());
14         lock.unlock();
15     }
16  
17     @Override
18     public void run() {
19         get();
20     }
21  
22     public static void main(String[] args) {
23         Test ss = new Test();
24         new Thread(ss).start();
25         new Thread(ss).start();
26         new Thread(ss).start();
27     }
28 }

两个例子最后的结果都是正确的,即 同一个线程id被连续输出两次。

结果如下:

Threadid: 8
Threadid: 8
Threadid: 10
Threadid: 10
Threadid: 9
Threadid: 9

可重入锁最大的作用是避免死锁
我们以自旋锁作为例子,

01 public class SpinLock {
02     private AtomicReference<Thread> owner =newAtomicReference<>();
03     public void lock(){
04         Thread current = Thread.currentThread();
05         while(!owner.compareAndSet(null, current)){
06         }
07     }
08     public void unlock (){
09         Thread current = Thread.currentThread();
10         owner.compareAndSet(current, null);
11     }
12 }

对于自旋锁来说,
1、若有同一线程两调用lock() ,会导致第二次调用lock位置进行自旋,产生了死锁
说明这个锁并不是可重入的。(在lock函数内,应验证线程是否为已经获得锁的线程)
2、若1问题已经解决,当unlock()第一次调用时,就已经将锁释放了。实际上不应释放锁。
(采用计数次进行统计)
修改之后,如下:

01 public class SpinLock1 {
02     private AtomicReference<Thread> owner =newAtomicReference<>();
03     private int count =0;
04     public void lock(){
05         Thread current = Thread.currentThread();
06         if(current==owner.get()) {
07             count++;
08             return ;
09         }
10  
11         while(!owner.compareAndSet(null, current)){
12  
13         }
14     }
15     public void unlock (){
16         Thread current = Thread.currentThread();
17         if(current==owner.get()){
18             if(count!=0){
19                 count--;
20             }else{
21                 owner.compareAndSet(current,null);
22             }
23  
24         }
25  
26     }
27 }

该自旋锁即为可重入锁。

转载自并发编程网

java锁的种类以及辨析(转载)的更多相关文章

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

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

  2. Java锁的种类以及辨析

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

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

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

  4. Java锁的种类

    转载自:---->http://ifeve.com/java_lock_see/ Java锁的种类以及辨析锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchroniz ...

  5. 多线程编程-- part5 锁的种类以及辨析

    java中的锁,可以分为同步锁和JUC包中的锁. 同步锁 通过synchronized关键字进行同步,实现对竞争资源的互斥访问的锁,. 原理:对于每一个对象,有且只有一个同步锁,在同一时间点,所有的线 ...

  6. java - 锁的种类及详解

    锁类型 锁根据其特性能够划分出各种各样的锁类型,该文主要介绍以下锁的作用及特性 乐观锁/悲观锁 独享锁/共享锁 互斥锁/读写锁 可重入锁 公平锁/非公平锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 ...

  7. java 锁 简介(转)

    转自 https://www.cnblogs.com/hustzzl/p/9343797.html 1. Java锁的种类 在笔者面试过程时,经常会被问到各种各样的锁,如乐观锁.读写锁等等,非常繁多, ...

  8. Java并发(九)【转载】不可不说的Java“锁”事

    转载自 美团技术团队,原文链接 不可不说的Java“锁”事 前言 Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率.本文旨在对锁相关源码(本文中的源码来自JDK ...

  9. 转 : 深入解析Java锁机制

    深入解析Java锁机制 https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw%3D%3D&mid=2247485524&idx=1&s ...

随机推荐

  1. SNP命名

    SNP命名 [2016-11-24]       奶茶妹妹是谁,京东老板娘,咦?章泽天!没错! 国民老公是谁?万达少东家,王健林儿子,王思聪!恭喜你又答对了! 函数是谁?这不是数学上的名词吗?不对,是 ...

  2. BZOJ1088或洛谷2327 [SCOI2005]扫雷

    BZOJ原题链接 洛谷原题链接 很容易发现答案就只有\(0,1,2\)三种答案,而且只要知道第一个格子是否有雷就可以直接顺推下去了. 所以我们跑一次首位有雷,跑一次首位无雷判断是否可行即可. #inc ...

  3. spring jpetstore研究入门(zz)

    spring jpetstore研究入门 分类: java2008-12-21 23:25 561人阅读 评论(2) 收藏 举报 springstrutsibatissearchweb框架servle ...

  4. “matplotlib display text must have all code points < 128 or use Unicode strings”解决方法

    import sys reload(sys) sys.setdefaultencoding('utf-8') 插入以上代码,便可解决.

  5. 【WebService】WebService之WSDL文档深入分析(三)

    WSDL概念 WSDL(网络服务描述语言,Web Services Description Language)是一门基于 XML 的语言,用于描述 Web Services 以及如何对它们进行访问. ...

  6. NOIP2017普及组T2题解

    还是神奇的链接 上面依然是题目. 这道题依然很简单,比起2015年的普及组t2好像还是更水一些. 不过这道题能讲的比第一题多. 我们一起来看一下吧! 这一题,我们首先将书的编号全部读入,存在一个数组里 ...

  7. mysql数据库进阶篇

    一.连表操作 1)为何需要连表操作 .把所有数据都存放于一张表的弊端 .表的组织结构复杂不清晰 .浪费空间 .扩展性极差 2)表设计,分析表与表之间的关系 寻找表与表之间的关系的套路 举例:emp表 ...

  8. AOP (切点表达式讲解)

    Spring EL表达式:: 1.execution 表达式 语法格式: execution(返回类型.包名.类名.方法名(参数表)) exection(*.com.xxx.AService.*(.. ...

  9. django之补充

    一 QuerySet类型 QuerySet类型:只和orm有关,如果一涉及数据库,就会有QuerySet类型的出现. QuerySet切片操作:QuerySet是支持切片操作的,不过不能放负数.查询集 ...

  10. MySQL mysqldump 数据备份

    1.mysqldump 命令工具说明 参数注解: mysqldump 是采用SQL 级别的备份机制,它将数据表导成 SQL 脚本文件,在不同的 MySQL 版本之间升级时相对比较合适,这也是最常用的备 ...