可重入锁 自旋锁,看这一篇就够了!

在多线程编程中,锁是常用地控制并发的机制,对于临界区的资源,需要保证线程之间互斥地访问。

1. 可重入锁

可重入锁,也叫做递归锁,指的是多次对同一个锁进行加锁操作,都不会阻塞线程。实现思路:记录当前锁正在被哪个线程使用,采用计数来统计lock和unlock的调用次数。正常情况下,lock和unlock的调用次数应该相等,如果不相等就会死锁。

public class Test implements Runnable {
    ReentrantLock lock = new ReentrantLock(); //定义一个可重入锁

    public void get() {
        lock.lock(); //第一次调用lock()
        System.out.println(Thread.currentThread().getId());
        set();
        lock.unlock();
    }

    public void set() {
        lock.lock(); //第二次调用lock(),而且会成功,说明lock是可重入锁
        System.out.println(Thread.currentThread().getId());
        lock.unlock();
    }

    @Override
    public void run() {
        get();
    }

    public static void main(String[] args) {
        Test ss = new Test();
        new Thread(ss).start();
        new Thread(ss).start();
        new Thread(ss).start();
    }
}

2. 自旋锁

首先,看看初级的自旋锁实现方式:

public class SpinLock {
    private AtomicReference<Thread> owner =new AtomicReference<>();
    public void lock(){
        Thread current = Thread.currentThread();
        while(!owner.compareAndSet(null, current)){
        }
    }

    public void unlock (){
        Thread current = Thread.currentThread();
        owner.compareAndSet(current, null);
    }
}

实现思路:通过CAS(CompareAndSet)原子操作来更新变量。如果CAS返回true,表示获得了锁;否则,需要通过while循环检查,直到获得锁为止,这也是为什么叫做自旋锁的原因,需要不停的尝试获取锁。

2.1 初级版本的问题
  1. 同一线程前后两次调用lock(),会导致第二次调用lock时进行自旋,产生了死锁(因为第一次调用lock()之后,还没有unlock),说明这个锁不是可重入的。
  2. 如果问题一已经解决,当第一次调用unlock()时,就已经将锁释放了。实际上不应释放锁。
2.2 解决方案
  1. 针对问题一:在lock函数内,应验证线程是否为已经获得锁的线程
  2. 针对问题二:采用计数进行统计
public class SpinLock {
    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);
            }
        }
    }
}

3. 参考资料

  1. Java锁的种类以及辨析(四):可重入锁
  2. 面试必问的CAS,你懂了吗?

原文地址:https://www.cnblogs.com/wengle520/p/12368689.html

时间: 2024-08-07 14:47:11

可重入锁 自旋锁,看这一篇就够了!的相关文章

转:【Java并发编程】之一:可重入内置锁

每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法. 当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞.然而,由于内置锁是可重入的,因此如果摸个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功."重入"意味着获取锁的操作的粒度是"线程",而不是调用.重入的一种实现 方法是,为每个锁关联一个

Java并发编程(1):可重入内置锁

每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法. 当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞.然而,由于内置锁是可重入的,因此如果摸个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功."重入"意味着获取锁的操作的粒度是"线程",而不是调用.重入的一种实现方法是,为每个锁关联一个获

【分布式锁】Redis实现可重入的分布式锁

一.前言 之前写的一篇文章<细说分布式锁>介绍了分布式锁的三种实现方式,但是Redis实现分布式锁关于Lua脚本实现.自定义分布式锁注解以及需要注意的问题都没描述.本文就是详细说明如何利用Redis实现重入的分布式锁. 二.方案 死锁问题 当一个客户端获取锁成功之后,假如它崩溃了导致它再也无法和 Redis 节点通信,那么它就会一直持有这个锁,导致其它客户端永远无法获得锁了,因此锁必须要有一个自动释放的时间.   我们需要保证setnx命令和expire命令以原子的方式执行,否则如果客户端执行

互斥锁,自旋锁与自适应自旋锁

线程安全与锁的优化 互斥锁: 从 实现原理上来讲,Mutex属于sleep-waiting类型的锁.例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上.假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进

Java线程并发中常见的锁--自旋锁 偏向锁

随着互联网的蓬勃发展,越来越多的互联网企业面临着用户量膨胀而带来的并发安全问题.本文着重介绍了在java并发中常见的几种锁机制. 1.偏向锁 偏向锁是JDK1.6提出来的一种锁优化的机制.其核心的思想是,如果程序没有竞争,则取消之前已经取得锁的线程同步操作.也就是说,若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,就无需再进行相关的同步操作了,从而节约了操作时间,如果在此之间有其他的线程进行了锁请求,则锁退出偏向模式.在JVM中使用-XX:+UseBiasedLocking pac

Java中的多线程你只要看这一篇就够了

Java中的多线程你只要看这一篇就够了 引 如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其实只有一半对,因为反应"多角色"的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的"生产者,消费者模型". 很多人都对其中的一些概念不够明确,如同步.并发等等,让我

Java中的多线程=你只要看这一篇就够了

如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型”. 很多人都对其中的一些概念不够明确,如同步.并发等等,让我们先建立一个数据字典,以免产生误会. 多线程:指的是这个程序(一个进程)运

iOS动画详解(学习动画看这一篇就够了)

iOS动画详解(学习动画看这一篇就够了) 一.基础知识 CAAnimation.png 二.CABasicAnimation 1. 动画的属性和解释 2.属性值的解释 repeatCount : 如果在swift中需要一直不断重复:Float.infinity,OC:HUGE_VALF timingFunction: timingFunction.png kCAMediaTimingFunctionLinear--在整个动画时间内动画都是以一个相同的速度来改变.也就是匀速运动.一个线性的计时函数

中后台产品的表格设计,看这一篇就够了(原型规范下载)

中后台产品的表格设计,看这一篇就够了(原型规范下载) 2018年4月16日luodonggan 中后台产品的表格设计,看这一篇就够了(原型规范下载) 经过了将近一年的后台产品经历,踩了很多坑,试了很多错,也学习到了很多东西,目前也形成了自己的一套规范.本文将其中的部分收获汇总成文,希望能够对大家有所帮助. 后台产品有一个很重要.常见的元素,就是表格.表格承担着详情入口.数据展示的功能,看似简单,其实里面的细节特别多.在以效率为最重要的需求的后台产品中,如何设计一个能够高效率地进行查看和编辑的表格

源码时代前端干货分享| AE如何实现文字消散效果?看这一篇就够了!

源码时代前端干货分享| AE如何实现文字消散效果?看这一篇就够了!首先:1.新建合成IMAge012.新建一个纯色层做背景(如果自带背景可以忽略)02 3.新建文本图层034.打上文字,调整文字大小,让文字在画布中居中04 然后:5.给文字添加一个蒙版05 6.在0秒的位置的蒙版路径打上关键帧067.0秒的时候蒙版路径最大,3秒的时候蒙版路径最小078.继续添加蒙版羽化089.再新建一个合成为置换0910.新建一个纯色的白图层1011.给它添加一个蒙版1112.在蒙版路径上打上关键帧13.0秒的