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. Android开发之利用ViewPager实现页面的切换(仿微信、QQ)

    这里利用ViewPager实现页面的滑动,下面直接上代码: 1.首先写一个Activity,然后将要滑动的Fragment镶嵌到写好的Activity中. Activity的布局文件:activity ...

  2. FastDFS集群安装

    集群安装1.FastDFS安装 Tracker,nginx 192.168.56.113       192.168.56.114 storage group1      192.168.56.115 ...

  3. What is MaxiSys Pro MS908P Software Advantage

    The Autel Maxisys Pro Diagnostic Platform is an evolutionary smart solution for specialized automoti ...

  4. UVA-1364.Knights of the Round Table 无向图BCC

    题目链接:https://vjudge.net/problem/UVA-1364 题意:有n个人参加会议,互相憎恨的人不能坐在相邻的位置,并且每个会议参加的人数必须是奇数,求有多少个人不能参加任何一个 ...

  5. Netsharp下微信菜单以及OAuth

    一.OAuth介绍 在微信开发中,当打开一个页面是,业务场景一般会基于粉丝绑定用户信息,即页面需要基于粉丝和用户的身份处理业务逻辑. 在微信中打开一个页面有三个场景: 1.文本回复中直接包含url 2 ...

  6. Android中关于使用空格对齐文字

    前言:今日编写新项目UI时,突然遇到文本有长有短无法对齐的问题(汗,以前竟从未遇到也从未考虑过这小小的问题),在资源文件中尝试Tab键.space空格键,发现效果都不能很好的实现,无奈只得请求度娘的协 ...

  7. centos配置虚拟用户再也不用那么麻烦了

    http://wiki.centos.org/HowTos/Chroot_Vsftpd_with_non-system_users yum install -y vsftpd db4-utils vs ...

  8. 【Linux】CentOS 7.4 安装 MySQL 8.0.12 解压版

    安装环境/工具 1.Linux(CentOS 7.4版) 2.mysql-8.0.12-el7-x86_64.tar.gz 安装步骤 参考:https://dev.mysql.com/doc/refm ...

  9. 【Web】Nginx 反向代理与负载均衡

    反向代理 反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客 ...

  10. jquery中ajax处理跨域的三大方式

    一.处理跨域的方式: 1.代理 2.XHR2 HTML5中提供的XMLHTTPREQUEST Level2(及XHR2)已经实现了跨域访问.但ie10以下不支持 只需要在服务端填上响应头: ? 1 2 ...