并发编程的关键——LOCK

并发编程的关键——LOCK

  • 锁的分类
  • synchronized
    • 万物即可为锁
    • synchronized的实现
    • 锁升级
  • Lock
    • AQS
      • LockSupport
      • CLH
      • CAS
  • Lock实现
    • ReentrantLock
    • 阻塞方法acquire
    • ReadWriteLock
      • ReentrantReadWriteLock
      • StampedLock

锁的分类

  • 公平锁/非公平锁:
    – 公平的意思是多个线程按照申请锁的顺序来获取锁。
    – 公平锁通常会造成系统更低的吞吐量
    – 非公平锁可能会出现线程饥饿的现象
    – ReentranLock:默认是非公平锁,可以通过构造参数指定为公平锁。
    – synchronized:默认是非公平锁,并且不能变为公平锁。
  • 独享锁/共享锁:
    – 独享的意思是一个锁只能被一个线程持有。
    – ReentranLock是独享锁
    – ReadWriteLocK的读锁是共享锁,写锁是独享锁。
    – synchronized是独享锁
  • 互斥锁/读写锁: 独享锁和共享锁的具体实现。
  • 乐观锁/悲观锁:
    – 乐观锁认为数据不一定会被修改,所以不会加锁,而是不断尝试更新。
    – 悲观锁认为数据会被修改,所以采用加锁的方式实现同步。
    – 乐观锁适合读操作多余写操作的系统。
  • 分段锁:一种锁的设计,例如ConcurentHashMap就使用了分段锁。
  • 偏向锁/轻量级锁/重量级锁:synchronized的三种状态
    – 偏向锁指同一段同步代码一直被一个线程访问,那么该线程就会自动获取这个锁,以降低获取锁的代价。
    – 轻量级锁:当前锁是偏向锁,并且被另一个线程访问,偏向锁会升级成轻量级锁,其他线程会通过自旋(CAS)的形式尝试获取锁。不会阻塞其他线程。
    – 重量级锁:当前锁是轻量级锁,另一个线程自旋到一定次数的时候还没获取到该锁,轻量级锁就会升级为重量级锁,会阻塞其他线程。
  • 自旋锁/调度锁:
    – 自旋锁:反复参数直到满足条件。
    – 调度锁:跟系统调度机构交互,实现并发控制。
  • 可重入锁:指同一线程再外层方法获取锁的时候,进入内层方法时会自动获取锁。

synchronized

synchronized是基于JVM实现的锁。

万物即可为锁

//给对象上锁----锁对象
synchronized Object obj = new Object();
//给方法上锁---锁对象
public synchronized void function(){}
//给静态方法上锁---锁class对象
public synchronized static void funciton(){}

同一个实例的两个方法都有synchronize锁,当方法1被一个线程上锁后,另一个线程能进入方法2吗?

方法上加锁,锁的是对象,静态方法上加锁,锁的是class对象,既然如此,答案肯定是不能进入。代码验证:

public class LockService {
    public synchronized void lock1(){
        System.out.println(Thread.currentThread().getName()+"进入lock1");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"离开lock1");
    }

    public synchronized void lock2(){
        System.out.println(Thread.currentThread().getName()+"进入lock2");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"离开lock2");
    }
}

public class LockTest {
    public static void main(String[] args) {
        LockService lockService = new LockService();
        new Thread(lockService::lock1).start();
        new Thread(lockService::lock2).start();
        System.out.println("等待任务执行完成");
    }
}

Thread-0进入lock1
等待任务执行完成
Thread-0离开lock1
Thread-1进入lock2
Thread-1离开lock2

疑问? synchronized(this) {}是锁代码块?这不是锁当前对象吗?

synchronized的实现

synchronized是通过对象头的锁标志位实现对象加锁的。
在这里插入图片描述
1.5以前synchronized是重量级锁,悲观锁,来就会加锁。
1.6以后优化了,有了锁升级的过程。

锁升级

1.6以后被synchronized标记的对象存在一个锁升级的过程,依次是:
在这里插入图片描述

注意:锁升级是按无锁>偏向锁>轻量级锁>重量级锁进行升级的,并且除了偏向锁可以重置为无锁外,锁是无法降级的。

Lock

lock是一个接口,用代码来实现锁。
在这里插入图片描述

AQS

AQS是jdk种Lock的实现基础,AQS使用模板方法模式,提供一个框架来实现依赖于FIFO等待队列的阻塞锁和相关的同步器(信号量、事件等),底层实现是volatile修饰的state和CAS。

public abstract class AbstractQueuedSynchronizer 
	extends AbstractOwnableSynchronizer 
	implements java.io.Serializable {
	  /**
	 * 等待队列的NODE节点类,等待队列是LCH锁队列的一种变种。CLH锁通常用于自旋锁。
	 * 相反,等待队列将它们用于阻塞同步器,但使用相同的基本策略,即在上一节点中保留有关线程的一些控制信息。
     * 当一个线程在等待某个资源时,它会被放入等待队列中,等待队列中的每个元素都是一个NODE对象。
     *      +------+  prev +-----+       +-----+
     * head |      | <---- |     | <---- |     |  tail
     *     +------+       +-----+       +-----+
     */
    static final class Node{
	    /** 用于指示节点正在共享模式下等待的标记 */
    	static final Node SHARED = new Node();
    	/** 用于指示节点正在独占模式下等待的标记 */
        static final Node EXCLUSIVE = null;    
        /**
         * waitStatus是AQS的重要属性,表示当前节点的状态。有五种取值:
         * CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
         * SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。
         * CONDITION(-2):表示节点在条件等待队列中,当其他线程调用了Condition的signal()方法之后,该节点就会从条件等待队列移动到同步队列中。
         * PROPAGATE(-3):表示当前节点需要被传播或者传递到下一个节点。
         *  当一个线程在获取资源时,如果当前节点不是头节点,并且前继节点的状态为SIGNAL或CONDITION,那么当前节点的状态将被更新为PROPAGATE。
         *  这个状态的作用是让当前节点能够将前继节点的状态传递下去,从而让下一个节点有机会获取资源。
         *  0:表示线程节点进入就绪状态,可以继续尝试获取锁了。
         */
        volatile int waitStatus;
        //连接到前一个节点,当前节点的waitStatus检查依赖前一节点。入队时分配,出队时设为null(为了GC)	。
         volatile Node prev;
         //连接到下一节点,
         volatile Node next;
         //将此节点排入队列的线程。在构造时初始化,使用后为null
         volatile Thread thread;
         Node nextWaiter;
     }
	//等待队列的头和尾
	private transient volatile Node head;
    private transient volatile Node tail;
    /**
    * 同步状态,有三种状态:
    *  0:没有线程持有
    *  1:已被线程持有
    *  >1: 被线程多次持有
    * 当一个线程来尝试加锁时,会对state进行CASE操作,将state从0改为1,如果操作成功,则将线程信息设置成自己。如果操作失败,则会进入等待队列。如果线程需要释放锁时会对state进行减1,如果减1后为0,则会彻底释放锁,将加锁线程置为null,然后从等待队列唤醒下一个线程。
    */
    private volatile int state;
    /**
    * Condition同样时一个队列,用于维护等待队列(条件队列)。Condition是基于Node实现的。
    * 每一个Condition都拥有一个等待队列(NODE队列),一个Lock可以有多个Condition,每一个Condition都是对应了一个单独的条件。
    */
	public class ConditionObject implements Condition, java.io.Serializable {
	     private transient Node firstWaiter;
	     private transient Node lastWaiter;
	}
}

LockSupport

LockSupport通过许可(permit)实现线程挂起、挂起线程唤醒功能。许可可以理解为每个已启动线程维持的一个int类型状态位counter。线程分别通过执行LockSupport静态方法park()、unPark()方法来完成挂起、唤醒操作2。LockSupport通常不会被直接使用,更多是作为锁实现的基础工具类1。

CLH

CLH是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先入先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制1。

CAS

CAS全称为Compare And Swap,是unsafe的一个方法。CAS机制的原理是利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法,该操作时原子的。
CAS操作包括三个操作数:内存位置V、预期原值A和新值B。当内存位置V的值与预期原值A相匹配时,将内存位置V的值设置为新值B,并且返回true;否则,不做任何操作,并返回false。

Lock实现

上面对AQS的概念还是比较模糊,下面将从锁的具体实现来理解下AQS

ReentrantLock

//提供锁实现的同步基础,分为以下公平和非公平版本。使用AQS状态表示锁上的挂起次数。
 private final Sync sync;
 abstract static class Sync extends AbstractQueuedSynchronizer {
	 //……
 	final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
 }
//非公平锁
 static final class NonfairSync extends Sync {
        /**
         * 立即尝试获取锁,否则以独占锁形式进入队列获取锁。
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
	//公平锁
	static final class FairSync extends Sync {
		/**
		* 直接进入队列获取锁
		*/
        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
   	public void lock() {
        sync.lock();
    }
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

注意看lock()方法,非公平锁和公平锁的差异就是:

  • 非公平锁会在lock的时候不管三七二十一,先尝试获取锁一次,如果获取失败,则会再查看下state是不是0,如果是0,再抢一次锁。如果没抢到才会乖乖入队。
  • 而公平锁则是查看state是否为0,并且是否其他线程在等待,如果没有才会尝试加锁。

lock和tryLock的区别是:

  • lock:获取锁失败会阻塞。
  • tryLock:获取锁失败则返回false,成功则返回true。不会阻塞。

阻塞方法acquire

	/*********AQS*****************/
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
	/**
	*  在一个死循环里面,不断检测上一个节点的状态,直到上一个节点状态为SIGNAL则将当前线程挂起来等待唤醒或被中断
	*/
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                /**
                *shouldParkAfterFailedAcquire只有检测到前一个节点状态为SIGNAL才会返回true,此时当前节点才能安全的挂起。
                *parkAndCheckInterrupt负责将当前线程挂起,并判断是否中断。如果线程未中断,则继续阻塞;如果线程中断,则抛出InterruptedException异常
                */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
 

ReadWriteLock

ReadWriteLock读写锁是一个接口,定义了两个获取锁的接口readLockwriteLock,在jdk里面其有两个实现:
在这里插入图片描述

ReentrantReadWriteLock

ReentrantReadWriteLock故名思意,可重入读写锁。
读写锁的读锁和写锁的关系是什么?

  • 读锁之间不互斥,可多个线程同时获取;
  • 读锁与写锁互斥,即写锁已经被某个线程获取时,其他线程不能获取读锁;读锁被某个线程获取时,其他线程不能获取写锁。
  • 写锁之间互斥,即一次只能有一个线程获取写锁;
  • 这两种锁之间会有优先级,当有等待的线程时,写锁锁住优先于读锁锁住;

锁降级是什么?

锁降级是说一个线程在拥有写锁的情况下可以不释放写锁,直接获取读锁,然后再释放写锁。这个过程就完成了锁降级。锁降级的好处是可以在写操作后直接进行读操作,避免了无谓的锁竞争和线程阻塞,提高了程序的并发性能。需要注意的是,锁降级是可逆的,即读锁可以再升级为写锁。但是锁升级的操作会导致死锁的风险,因此在使用锁降级时需要谨慎处理锁的获取和释放顺序。

StampedLock

StampedLock是JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的读性能的优化。
StampedLock有一个stamp变量(戳记,long类型)代表了锁的状态。当stamp返回零时,表示线程获取锁失败。并且,当释放锁或者转换锁的时候,都要传入最初获取的stamp值1。StampedLock有以下3种模式:

  • 悲观读锁。与ReadWriteLock的读锁类似(这里的读锁不可重入),多个线程可以同时获取悲观读锁,悲观读锁是一个共享锁。
  • 乐观读锁。相当于直接操作数据,不加任何锁,连读锁都不要。在操作数据前并没有通过CAS设置锁的状态,仅仅通过位运算测试。
  • 写锁。与ReadWriteLock的写锁类似,写锁和悲观读锁是互斥的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/99914.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决vue项目首行报红( ESLint 配置)和新建的vue文件首行报红问题

目录 前情提要&#xff1a; 修改ESLint 配置 新建的vue文件首行还是报红 报红原因&#xff1a; 解决方法&#xff1a; 前情提要&#xff1a; 在网上查到的方法可能是在package.json文件或者.eslintrc.js文件中添加 requireConfigFile: false 如果此方法对你的错误不起作用…

2020ICPC南京站

K K Co-prime Permutation 题意&#xff1a;给定n和k&#xff0c;让你构造n的排列&#xff0c;满足gcd(pi, i)1的个数为k。 思路&#xff1a;因为x和x-1互质&#xff0c;1和任何数互质&#xff0c;任何数和它本身不互质 当k为奇数时&#xff0c;p11&#xff0c;后面k-1个数…

云原生Kubernetes:二进制部署K8S多Master架构(三)

目录 一、理论 1.K8S多Master架构 2.配置master02 3.master02 节点部署 4.负载均衡部署 二、实验 1.环境 2.配置master02 3.master02 节点部署 4.负载均衡部署 三、总结 一、理论 1.K8S多Master架构 (1) 架构 2.配置master02 &#xff08;1&#xff09;环境 关闭防…

yolov5自定义模型训练三

经过11个小时cpu训练完如下 在runs/train/expx里存放训练的结果&#xff0c; 测试是否可以检测ok 网上找的这张识别效果不是很好&#xff0c;通过加大训练次数和数据集的话精度可以提升。 训练后的权重也可以用视频源来识别&#xff0c; python detect.py --source 0 # webca…

盘点狼人杀中的强神与弱神 并评价操作体验

最初 强神是大家对猎人的称呼&#xff0c;但随着板子的增加 强神渐渐变成了强神神牌的统称。 狼人杀发展至今板子已经非常多了&#xff0c;而每个板子都会有不同的角色。 相同的是 大部分都会希望拿到一张强力神牌&#xff0c;这样能大大提高我们玩家的游戏体验&#xff0c;但其…

Vue2项目练手——通用后台管理项目第五节

Vue2项目练手——通用后台管理项目 首页组件布局面包屑&tag面包屑使用组件使用vuex存储面包屑数据src/store/tab.jssrc/components/CommonAside.vuesrc/components/CommonHeader.vue tag使用组件文件目录CommonTag.vueMain.vuetabs.js 用户管理页新增功能使用的组件页面布局…

Docker切换文件系统为VFS

一、介绍 Docker支持AUFS、Btrfs、Device Mapper、OverlayFS、VFS、ZFS六种不同的存储驱动。 1. AUFS AUFS是一种常见的存储驱动程序&#xff0c;它也使用了Linux内核的AUFS文件系统。它的优点是支持所有的Linux发行版&#xff0c;可以在不同的容器之间共享文件系统&#xf…

多因素认证与身份验证:分析不同类型的多因素认证方法,介绍如何在访问控制中使用身份验证以增强安全性

随着数字化时代的到来&#xff0c;信息安全问题变得愈发重要。在网络世界中&#xff0c;用户的身份往往是保护敏感数据和系统免受未经授权访问的第一道防线。单一的密码已经不再足够&#xff0c;多因素认证&#xff08;MFA&#xff09;应运而生&#xff0c;成为提升身份验证安全…

【RabbitMQ】RabbitMQ 服务无法启动。系统出错。发生系统错误 1067。进程意外终止。

问题描述 RabbitMQ 服务无法启动。 rabbitmq-service.bat startRabbitMQ 服务正在启动 . RabbitMQ 服务无法启动。系统出错。发生系统错误 1067。进程意外终止。原因分析 RabbitMQ和Erlang版本不匹配。 解决方案 查询并安装RabbitMQ版本对应Erlang版本 https://www.rabbitm…

解决window安装docker报错问题

第一次打开Docker Desktop后提示错误 试了网上版本都没用&#xff0c;后面发现是电脑没有下载相关虚拟机&#xff1a; 先点击链接下载wsl2&#xff0c;下载后命令行执行&#xff1a;dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /…

使用boost::geometry::union_ 合并边界(内、外):方案二

使用boost::geometry::union_ 合并边界&#xff08;内、外&#xff09;&#xff1a;方案二 typedef boost::geometry::model::d2::point_xy<double> boost_point; typedef boost::geometry::model::polygon<boost_point> boost_Polygon;struct Point {float x;floa…

ARM DIY(六)音频调试

前言 今天&#xff0c;调试一下音频 硬件焊接 硬件部分核心是 LM4871 音频功放芯片 对于 SOC 来讲很简单&#xff0c;就一个引脚 HPOUTL&#xff08;单声道&#xff09;&#xff1b;对于扬声器来讲也很简单&#xff0c;就两个引脚&#xff0c;插上就可以了。 另外一个关键点…

【Cadence】Calculator计算sp的3dB带宽

【Cadence】Calculator计算sp的3dB带宽 1.计算最大增益2.cross函数3. 3dB带宽 下面演示如何在Cadence计算s参数&#xff08;如增益&#xff09;的3dB带宽 1.计算最大增益 ymax函数 2.cross函数 cross函数可以计算经过y轴给定值对应的x坐标 edge number选择1是经过的第一个点…

DEAP库文档教程三-----创建类型

本节将继续展示如何通过creator创建类型以及如何使用toolbox如何对复杂问题进行初始化。 Particle的初始化--粒子初始化 一个Particle是另一个特殊类型的个体&#xff0c;这是因为通常情况下它有一个速度&#xff0c;并且有一个最优的位置需要去记忆。这种类型个体的创建与通…

探索云原生容器编排技术:如Kubernetes如何为大数据处理和AI模型的自动化部署带来便利

文章目录 1. 弹性伸缩2. 容器化3. 自动化部署4. 存储管理5. 服务发现和负载均衡6. 监控和日志记录7. 多云支持8. 多版本管理9. 安全性和隔离10. 社区支持和生态系统 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点…

Windows右键添加用 VSCODE 打开

1.安装VSCODE时 安装时会有个选项来添加&#xff0c;如下&#xff1a; ①将“通过code 打开“操作添加到windows资源管理器文件上下文菜单 ②将“通过code 打开”操作添加到windows资源管理器目录上下文菜单 说明&#xff1a;①②勾选上&#xff0c;可以对文件&#xff0c;目…

【CicadaPlayer】getPlayerBufferDuration分析

https://github.com/alibaba/CicadaPlayer/blob/release/0.4.4/mediaPlayer/SuperMediaPlayer.cpp核心关键函数int64_t SuperMediaPlayer::getPlayerBufferDuration(bool gotMax, bool internal)17个地方出现: getPlayerBufferDuration的durations 数组 分别 对音频、视频、字…

企业供应链数字化怎么做?企业数字化供应链流程落地方式

什么是供应链&#xff1f;简单来说&#xff0c;供应链是围绕客户需求&#xff0c;以提高产品流通各个环节的效率为目标&#xff0c;通过资源整合的方式来实现产品从设计、生产到销售、服务整个环节的组织形态。如同人工智能、区块链、5G等技术的发展带来的各种行业变化&#xf…

嵌入式岗位笔试面试专栏 - 岗位介绍

文章目录 一、嵌入式岗位的分类二、热门领域及公司三、发展前景四、技能要求沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将讲解嵌入岗位的工作职责 。 一、嵌入式岗位的分类 嵌入式软件工程师大致可以分为两种类型: 应用开发工程师驱动开发工程师应用工程…

汽车以太网协议栈

《大师说》栏目上线啦# 《大师说》栏目是怿星科技2023年推出的深度思考栏目&#xff0c;通过邀请内部专家&#xff0c;针对智能汽车行业发展、技术趋势等输出个性化的观点。每期一位大师&#xff0c;每位一个话题&#xff0c;本期由我们怿星的CTO虞胜伟&#xff0c;进行分享。…