Java锁自定义实现到aqs的理解

专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162

本文目标:

  1. 理解锁,能自定义实现锁
  2. 通过自定义锁的实现复习Thread和Object的相关方法
  3. 开始尝试理解Aqs, 这样后续基于Aqs的的各种实现将能更好的理解

目录

    • 锁的自定义实现
      • lock 自旋加锁
      • 改进1: 自旋加锁失败的尝试让出cpu(yield操作)
      • 改进2: yield 换成sleep
      • 改进3: 仿照ObjectMonitor,加上一个等待队列
    • AbstractQueuedSynchronizer
      • Aqs核心思想归纳
      • AbstractQueuedSynchronizer 抽象类的说明和实现
      • 同步器说明
      • Aqs独占模式
      • Aqs共享模式
      • 基于Aqs再次实现自定义锁

锁的自定义实现

线程基础和cas操作知晓后,可以开始自己实现锁了。

例子代码实现多线程的count++

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.TimeUnit;

class MyLock {
    volatile int status = 0;
    Unsafe unsafe;

    // 引用Unsafe需使用如下反射方式,否则会抛出异常java.lang.SecurityException: Unsafe
    public Unsafe reflectGetUnsafe() throws NoSuchFieldException, IllegalAccessException {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);//禁止访问权限检查(访问私有属性时需要加上)
        return (Unsafe) theUnsafe.get(null);
    }

    boolean compareAndSet(int expect, int newValue) {
        try {
            Field field = this.getClass().getDeclaredField("status");
            field.setAccessible(true);
            long offset = unsafe.objectFieldOffset(field);
            //cas操作
            return unsafe.compareAndSwapInt(status, offset, expect, newValue);
        } catch (Exception e) {
            return false;
        }
    }

    public MyLock() {
        try {
            unsafe = reflectGetUnsafe();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void lock() {
        while (!compareAndSet(0, 1)) {
        }
    }

    public void unlock() {
        compareAndSet(1, 0);
    }

}

public class Main {
    static int count = 0;
    static int N = 100;


    public static void main(String[] args) throws Exception {

        MyLock lock = new MyLock();

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < N; i++) {
            Thread thread = new Thread(() -> {
                try {
                    lock.lock();
                    TimeUnit.MILLISECONDS.sleep(1);
                    count++;
                } catch (Exception e) {

                } finally {
                    lock.unlock();
                }
            }, "myThread-" + i);
            list.add(thread);
        }
        for (Thread thread : list) {
            thread.start();
        }
        for (Thread thread : list) {
            thread.join();
        }

        System.out.println("count:" + count);
    }
}

lock 自旋加锁

volatile int status = 0;

public void lock() {
    while (!compareAndSet(0, 1)) {
    }
}

问题:没有竞争得到锁的线程会一直占用CPU资源进行CAS操作,假如一个线程耗费n秒处理业务逻辑,那另外一个线程就会白白花费n秒的CPU资源在那里自旋。

改进1: 自旋加锁失败的尝试让出cpu(yield操作)

所以可以yield尝试让出CPU(可复习:https://blog.csdn.net/qq_26437925/article/details/145400968)

 public void lock() {
   while (!compareAndSet(0, 1)) {
        Thread.yield();
    }
}

问题:yield是将线程设置为就绪状态,需要获取到CPU时间片才能执行变成真正的RUNNABLE状态。但是当线程很多的时候,重新竞争的时候很可能某些线程老是获取不到CPU执行,这样就会出现线程饥饿现象

改进2: yield 换成sleep

public void lock() {
    while (!compareAndSet(0, 1)) {
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (Exception e) {

        }
    }
}

sleep 也是让出CPU,但是和yield不同,其是把线程设置为TIMED_WAITING状态(A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state)。不过sleep使用的问题是,不确定sleep多久

改进3: 仿照ObjectMonitor,加上一个等待队列

获取不到的锁的线程,加入到一个等待队列中,释放其CPU; 当有线程要释放锁了,则从头部队列移除线程,开始继续获取锁

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.TimeUnit;


class MyLock {
    volatile int status = 0;
    Unsafe unsafe;
    Queue<Thread> waitQueue;

    // 引用Unsafe需使用如下反射方式,否则会抛出异常java.lang.SecurityException: Unsafe
    public Unsafe reflectGetUnsafe() throws NoSuchFieldException, IllegalAccessException {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);//禁止访问权限检查(访问私有属性时需要加上)
        return (Unsafe) theUnsafe.get(null);
    }

    boolean compareAndSet(int expect, int newValue) {
        try {
            Field field = this.getClass().getDeclaredField("status");
            field.setAccessible(true);
            long offset = unsafe.objectFieldOffset(field);
            //cas操作
            return unsafe.compareAndSwapInt(status, offset, expect, newValue);
        } catch (Exception e) {
            return false;
        }
    }

    public MyLock() {
        try {
            unsafe = reflectGetUnsafe();
            waitQueue = new ArrayDeque<>();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void lock() {
        while (!compareAndSet(0, 1)) {
            park(Thread.currentThread());
        }
    }

    public void unlock() {
        compareAndSet(1, 0);

        // 有线程要释放锁了,公平方式,头部线程可以不等待了, 开始去获取锁
        // 1.得到要唤醒的线程头部线程
        Thread t = waitQueue.poll();
        // 2. 唤醒等待线程
        if (t != null) {
            unsafe.unpark(t);
        }
    }


    private void park(Thread thread) {
        if (waitQueue.contains(thread)) {
            return;
        }
        // 将线程加入到等待队列中
        waitQueue.add(thread);
        // 将当期线程释放CPU 阻塞
        thread.yield();
    }
}

public class Main {
    static int count = 0;
    static int N = 100;


    public static void main(String[] args) throws Exception {

        MyLock lock = new MyLock();


        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < N; i++) {
            Thread thread = new Thread(() -> {
                try {
                    lock.lock();
                    TimeUnit.MILLISECONDS.sleep(1);
                    count++;
                } catch (Exception e) {

                } finally {
                    lock.unlock();
                }
            }, "myThread-" + i);
            list.add(thread);
        }
        for (Thread thread : list) {
            thread.start();
        }
        for (Thread thread : list) {
            thread.join();
        }

        System.out.println("count:" + count);
    }
}

AbstractQueuedSynchronizer

如上上述自定义锁实现能够理解,那么就是理解aqs了

  • 锁的实现框架

参考:美团技术文章:从ReentrantLock的实现看AQS的原理及应用

参考:《The java.util.concurrent Synchronizer Framework》 JUC同步器框架(AQS框架)原文翻译

论文地址

docs api

Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. This class is designed to be a useful basis for most kinds of synchronizers that rely on a single atomic int value to represent state. Subclasses must define the protected methods that change this state, and which define what that state means in terms of this object being acquired or released. Given these, the other methods in this class carry out all queuing and blocking mechanics. Subclasses can maintain other state fields, but only the atomically updated int value manipulated using methods getState(), setState(int) and compareAndSetState(int, int) is tracked with respect to synchronization.

Aqs核心思想归纳

AQS核心思想是:如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中

CLH:Craig、Landin and Hagersten队列,链表结构,AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。

在这里插入图片描述

在这里插入图片描述

AQS使用一个volatile的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对state值的修改。

AbstractQueuedSynchronizer 抽象类的说明和实现

  1. AbstractQueuedSynchronizer 提供了一个框架,用来实现blocking locks 和 一些同步器,且是基于一个FIFO队列的
  2. AbstractQueuedSynchronizer 被设计为使用一个single atomic {@code int} value来表示状态
  3. AbstractQueuedSynchronizer的子类必须去定义状态,并提供protected方法去操作状态:getState、setState以及compareAndSet
  4. 基于AQS的具体实现类必须根据暴露出的状态相关的方法定义tryAcquiretryRelease方法,以控制acquire和release操作。当同步状态满足时,tryAcquire方法必须返回true,而当新的同步状态允许后续acquire时,tryRelease方法也必须返回true。这些方法都接受一个int类型的参数用于传递想要的状态。

同步器说明

两个操作

  1. acquire操作:阻塞调用的线程,直到或除非同步状态允许其继续执行。
  2. release操作:则是通过某种方式改变同步状态,使得一或多个被acquire阻塞的线程继续执行。

同步器需要支持如下:

  • 阻塞和非阻塞(例如tryLock)的同步
  • 可选的超时设置,让调用者可以放弃等待
  • 通过中断实现的任务取消,通常是分为两个版本,一个acquire可取消,而另一个不可以

同步器的实现根据其状态是否独占而有所不同。独占状态的同步器,在同一时间只有一个线程可以通过阻塞点,而共享状态的同步器可以同时有多个线程在执行。一般锁的实现类往往只维护独占状态,但是,例如计数信号量在数量许可的情况下,允许多个线程同时执行。为了使框架能得到广泛应用,这两种模式都要支持。

j.u.c包里还定义了Condition接口,用于支持监控形式的await/signal操作,这些操作与独占模式的Lock类有关,且Condition的实现天生就和与其关联的Lock类紧密相关

Aqs独占模式

在这里插入图片描述

Aqs共享模式

在这里插入图片描述

基于Aqs再次实现自定义锁

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;


class MyLock  {

    private static class Sync extends AbstractQueuedSynchronizer {
		// 加锁的cas操作
        @Override
        protected boolean tryAcquire (int arg) {
            return compareAndSetState(0, 1);
        }

        @Override
        protected boolean tryRelease (int arg) {
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively () {
            return getState() == 1;
        }
    }

    private Sync sync = new Sync();

    public void lock () {
        sync.acquire(1);
    }

    public void unlock () {
        sync.release(1);
    }
}

public class Main {
    static int count = 0;
    static int N = 100;


    public static void main(String[] args) throws Exception {

        MyLock lock = new MyLock();


        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < N; i++) {
            Thread thread = new Thread(() -> {
                try {
                    lock.lock();
                    TimeUnit.MILLISECONDS.sleep(1);
                    count++;
                } catch (Exception e) {

                } finally {
                    lock.unlock();
                }
            }, "myThread-" + i);
            list.add(thread);
        }
        for (Thread thread : list) {
            thread.start();
        }
        for (Thread thread : list) {
            thread.join();
        }

        System.out.println("count:" + count);
    }
}

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

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

相关文章

html的字符实体和颜色表示

在HTML中&#xff0c;颜色可以通过以下几种方式表示&#xff0c;以下是具体的示例&#xff1a; 1. 十六进制颜色代码 十六进制颜色代码以#开头&#xff0c;后面跟随6个字符&#xff0c;每两个字符分别表示红色、绿色和蓝色的强度。例如&#xff1a; • #FF0000&#xff1a;纯红…

Golang 并发机制-1:Golang并发特性概述

并发是现代软件开发中的一个基本概念&#xff0c;它使程序能够同时执行多个任务&#xff0c;从而提高效率和响应能力。在本文中&#xff0c;我们将探讨并发性在现代软件开发中的重要性&#xff0c;并深入研究Go处理并发任务的独特方法。 并发的重要性 增强性能 并发在提高软…

three.js用粒子使用canvas生成的中文字符位图材质

three.js用粒子使用canvas生成中文字符材质 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Three.…

《逆向工程核心原理》第三~五章知识整理

查看上一章节内容《逆向工程核心原理》第一~二章知识整理 对应《逆向工程核心原理》第三章到第五章内容 小端序标记法 字节序 多字节数据在计算机内存中存放的字节顺序分为小端序和大端序两大类 大端序与小端序 BYTE b 0x12; WORD w 0x1234; DWORD dw 0x12345678; cha…

2025年数学建模美赛 A题分析(4)楼梯使用人数模型

2025年数学建模美赛 A题分析&#xff08;1&#xff09;Testing Time: The Constant Wear On Stairs 2025年数学建模美赛 A题分析&#xff08;2&#xff09;楼梯磨损分析模型 2025年数学建模美赛 A题分析&#xff08;3&#xff09;楼梯使用方向偏好模型 2025年数学建模美赛 A题分…

【cocos creator】【模拟经营】餐厅经营demo

下载&#xff1a;【cocos creator】模拟经营餐厅经营

29.Word:公司本财年的年度报告【13】

目录 NO1.2.3.4 NO5.6.7​ NO8.9.10​ NO1.2.3.4 另存为F12&#xff1a;考生文件夹&#xff1a;Word.docx选中绿色标记的标题文本→样式对话框→单击右键→点击样式对话框→单击右键→修改→所有脚本→颜色/字体/名称→边框&#xff1a;0.5磅、黑色、单线条&#xff1a;点…

高性能消息队列Disruptor

定义一个事件模型 之后创建一个java类来使用这个数据模型。 /* <h1>事件模型工程类&#xff0c;用于生产事件消息</h1> */ no usages public class EventMessageFactory implements EventFactory<EventMessage> { Overridepublic EventMessage newInstance(…

neo4j初识

文章目录 一 图论基础二 柯尼斯堡七桥问题2.1 问题背景2.2 欧拉的解决3.1 核心概念3.2 核心优势3.3 应用场景3.4 技术特性3.5 版本与部署3.6 示例&#xff1a;社交关系查询3.7 限制与考量 四 图论与 Neo4j 的关联4.1 数据建模4.2 高效遍历4.3 应用场景 五 示例&#xff1a;用 N…

吴恩达深度学习——超参数调试

内容来自https://www.bilibili.com/video/BV1FT4y1E74V&#xff0c;仅为本人学习所用。 文章目录 超参数调试调试选择范围 Batch归一化公式整合 Softmax 超参数调试 调试 目前学习的一些超参数有学习率 α \alpha α&#xff08;最重要&#xff09;、动量梯度下降法 β \bet…

行业规范要当作业务实体画出来吗

第五元素 总觉得这些没有逻辑的实体&#xff0c;在绘制的时候不应该绘出来&#xff0c;他们没有责任啊。 比如以下:查阅规范 感觉不太对 UMLChina潘加宇 你这个规范是一个电脑系统还是一本书 第五元素 是书 UMLChina潘加宇 书没有智能&#xff0c;唯一暴露的接口是“翻”…

冯·诺依曼体系结构

目录 冯诺依曼体系结构推导 内存提高冯诺依曼体系结构效率的方法 你使用QQ和朋友聊天时&#xff0c;整个数据流是怎么流动的&#xff08;不考虑网络情况&#xff09; 与冯诺依曼体系结构相关的一些知识 冯诺依曼体系结构推导 计算机的存在就是为了解决问题&#xff0c;而解…

Qt之数据库操作三

主要介绍qt框架中对数据库的增加&#xff0c;删除和修改功能。 软件界面如下 程序结构 tdialogdata.h中代码 #ifndef TDIALOGDATA_H #define TDIALOGDATA_H#include <QDialog> #include<QSqlRecord> namespace Ui { class TDialogData; }class TDialogData : pub…

8.攻防世界Web_php_wrong_nginx_config

进入题目页面如下 尝试弱口令密码登录 一直显示网站建设中&#xff0c;尝试无果&#xff0c;查看源码也没有什么特别漏洞存在 用Kali中的dirsearch扫描根目录试试 命令&#xff1a; dirsearch -u http://61.147.171.105:53736/ -e* 登录文件便是刚才登录的界面打开robots.txt…

算法基础学习——快排与归并(附带java模版)

快速排序和归并排序是两种速度较快的排序方式&#xff0c;是最应该掌握的两种排序算法&#xff0c; &#xff08;一&#xff09;快速排序&#xff08;不稳定的&#xff09; 基本思想&#xff1a;分治 平均时间复杂度&#xff1a;O(nlogn) / 最慢O(n^2) / 最快O(n) 步骤&…

51单片机开发——I2C通信接口

I2C是微电子通信控制领域广泛采用的一种总线标准。 起始和停止信号&#xff1a; void iic_start(void) {IIC_SDA1;//如果把该条语句放在SCL后面&#xff0c;第二次读写会出现问题delay_10us(1);IIC_SCL1;delay_10us(1);IIC_SDA0; //当SCL为高电平时&#xff0c;SDA由高变为低d…

力扣017_最小覆盖字串题解----C++

题目描述 我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针&#xff0c;一个用于「延伸」现有窗口的 r 指针&#xff0c;和一个用于「收缩」窗口的 l 指针。在任意时刻&#xff0c;只有一个指针运动&#xff0c;而另一个保持静止。我们在 s 上滑动…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.16 内存黑科技:缓冲区协议的底层突破

1.16 内存黑科技&#xff1a;缓冲区协议的底层突破 目录 #mermaid-svg-RmGabswVIrCh5olE {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-RmGabswVIrCh5olE .error-icon{fill:#552222;}#mermaid-svg-RmGabswVIrCh5o…

MySQL数据库(二)- SQL

目录 ​编辑 一 DDL (一 数据库操作 1 查询-数据库&#xff08;所有/当前&#xff09; 2 创建-数据库 3 删除-数据库 4 使用-数据库 (二 表操作 1 创建-表结构 2 查询-所有表结构名称 3 查询-表结构内容 4 查询-建表语句 5 添加-字段名数据类型 6 修改-字段数据类…

jstat命令详解

jstat 用于监视虚拟机运行时状态信息的命令&#xff0c;它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。 命令的使用格式如下。 jstat [option] LVMID [interval] [count]各个参数详解&#xff1a; option&#xff1a;操作参数LVMID&#xff1a;本…