Java并发编程-队列同步器(AbstractQueuedSynchronizer)

简介: 章节目录Lock接口与Synchronized的区别及特性队列同步器的接口与自定义锁示例队列同步器的实现分析1.Lock接口与Synchronized的区别及特性特性描述尝试非阻塞性的获取锁当前线程尝试获取锁(自旋获取锁)...

章节目录

  • Lock接口与Synchronized的区别及特性
  • 队列同步器的接口与自定义锁示例
  • 队列同步器的实现分析

1.Lock接口与Synchronized的区别及特性

特性 描述
尝试非阻塞性的获取锁 当前线程尝试获取锁(自旋获取锁),如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁
能被中断的获取锁 已获取锁的线程可以响应中断,当获取到锁的线程被中断时,可以抛出中断异常,同时锁会被释放
超时获取锁 在指定的截止时间之前获取锁,如果截止时间到了仍然没有获取到锁,则返回

注意:Lock接口的实现基本上都是通过聚合了一个同步器的子类来完成线程访问控制的

队里同步器的接口与定义锁示例

队列同步器定义:

队列同步器,是用来构建锁与其它同步组件的基础框架,基本数据结构与内容是:
1、int state -> state 标示同步状态;
2、内置的FIFO来完成获取同步状态的线程的排队工作。
AI 代码解读

队列同步器使用方式

1、子类通过继承同步器并实现它的抽象方法来管理同步状态;
2、实现过程中对同步状态的更改,通过
setState()setState(int newState)compareAndSetState(int expect,int newUpdateValue)
来进行操作,保证状态改变时原子性的、安全的;
3、实现同步器的子类被推荐为自定义同步组件的静态内部类;
4、同步器可以支持独占式的获取同步状态(ReentrantLock)、也可以支持共享
式的获取同步状态(ReentrantReadWriteLock)
AI 代码解读

对于同步器的关系可以这样理解:

  • 在锁的实现中聚合同步器,利用同步器实现锁的语义。
  • 锁面向使用者,它定义了使用者与锁的交互接口,隐藏了实现细节。
  • 同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步器状态管理、线程排队、等待与唤醒等底层操作。

2.队列同步器的接口与自定义锁示例

2.1 模板方法模式

同步器的设置是基于**模版方法模式**,使用者需要继承同步器并重写指定的方
法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板
方法,而这些模板方法将会调用使用者重写的方法。
AI 代码解读

2.2 重写同步器指定的方法

getState():获取当前同步状态
setState(int newState):设置当前同步状态
compareAndSetState(int expect,int update): 使用CAS设置当前的状态,该方
法保证状态设置的原子性
AI 代码解读

2.3 同步器可重写的方法

方法名称 描述
protected boolean tryAcquire(int arg) 独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态
protected boolean tryRelease(int arg) 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态(公平性获取锁)
protected int tryAcquireShared(int arg) 共享式获取同步状态,返回>=0的值,标示获取成功,反之获取失败
protected boolean tryReleaseShared(int arg) 共享式释放同步状态
protected boolean isHeldExclusively() 当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占

2.4 独占锁示例

package org.seckill.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 利用了模板方法模式
 */
public class Mutex implements Lock {

    private static class Sync extends AbstractQueuedSynchronizer {
        //是否处于占用状态
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        //当状态为0时获取锁
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //释放锁,将当前状态设置为0
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;

        }

        //返回一个condition,每个condition中都包含了一个condition队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    //仅需要将操作代理到Sync上即可
    private Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);//调用tryAccquire
    }

    //当前已获取锁的线程响应中断,释放锁,抛出异常,并返回
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);//尝试立即获取锁
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));//尝试超时获取锁
    }

    public void unlock() {
        sync.release(1);//释放锁
    }

    public Condition newCondition() {
        return sync.newCondition();
    }
}

AI 代码解读

总结-实现同步组件的方法

1. 独占锁Mutex 是一个自定义同步组件,它允许同一时刻只允许同一个线程占有锁。
2.Mutex中定义了一个私有静态内部类,该类继承了同步器并实现了独占式获取和释放同步状态。
3.在tryAcquire(int acquires)方法中,经过CAS设置成功(同步状态设置为1),则
代表获取了同步状态,而在tryRelease(int releases) 方法中只是将同步状态重
置为0
AI 代码解读

3 队列同步器的实现分析

3.1 同步队列数据结构

  • 同步器依赖内部的同步队列,即一个FIFO的队列,这个队列由双向链表实现。节点数据从 队列尾部插入,头部删除
  • node 数据结构
   struct node {
        node prev; //节点前驱节点
        node next; //节点后继节点
        Thread thread; //获取同步状态的线程
        int waitStatus;  //等待状态
        Node nextWaiter; //等待队列中的后继节点
   }
AI 代码解读

等待队列 后续篇章介绍到condition会有相关记录。

img_893ee1773804ef82128d84607a1a3ee4.png
同步队列基本结构

3.2 无法获取到同步状态的线程节点被加入到同步队列的尾部

本质上是采用 compareAndSetTail(Node expect,Node update),当一个线程成功的获取了同步状态
(或者锁),其他线程将无法获取到同步状态,转而被构造成为节点并加入到同步队列中,而这个加入队列的过程
必须要保证线程安全。所以采用了基于CAS的方式来设置尾节点的方法。
,需要传递当前节点认为的尾节点和当前节点,只有设置成功后,当前节点才正式与之前的尾节点建立关联。
AI 代码解读

3.3 成功获取同步状态

同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放
同步状态时,会唤醒后继节点,而后继节点将会在获取同步状态成功时,将自己设置为首节点。
AI 代码解读

3.4 独占式同步状态获取与释放

  • 前驱节点为头节点且能够获取同步状态的判断条件和线程进入同步队列 来获
    取同步状态是自旋的过程。
  • 设置首节点是通过获取同步状态成功的线程来完成的acquireQueued(node,args)完成的

独占式获取同步状态的流程图

img_f3f8e640aedfb403dd164dfac2a074e9.png
独占式同步状态(锁)获取流程

目录
打赏
0
0
0
0
6
分享
相关文章
k8s的出现解决了java并发编程胡问题了
Kubernetes通过提供自动化管理、资源管理、服务发现和负载均衡、持续交付等功能,有效地解决了Java并发编程中的许多复杂问题。它不仅简化了线程管理和资源共享,还提供了强大的负载均衡和故障恢复机制,确保应用程序在高并发环境下的高效运行和稳定性。通过合理配置和使用Kubernetes,开发者可以显著提高Java应用程序的性能和可靠性。
65 31
【YashanDB知识库】kettle同步大表提示java内存溢出
在数据导入导出场景中,使用Kettle进行大表数据同步时出现“ERROR:could not create the java virtual machine!”问题,原因为Java内存溢出。解决方法包括:1) 编辑Spoon.bat增大JVM堆内存至2GB;2) 优化Kettle转换流程,如调整批量大小、精简步骤;3) 合理设置并行线程数(PARALLELISM参数)。此问题影响所有版本,需根据实际需求调整相关参数以避免内存不足。
注解的艺术:Java编程的高级定制
注解是Java编程中的高级特性,通过内置注解、自定义注解及注解处理器,可以实现代码的高度定制和扩展。通过理解和掌握注解的使用方法,开发者可以提高代码的可读性、可维护性和开发效率。在实际应用中,注解广泛用于框架开发、代码生成和配置管理等方面,展示了其强大的功能和灵活性。
66 25
在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境
以上内容是一个简单的实现在Java后端中通过DockerClient操作Docker生成python环境并执行代码,最后销毁的案例全过程,也是实现一个简单的在线编程后端API的完整流程,你可以在此基础上添加额外的辅助功能,比如上传文件、编辑文件、查阅文件、自定义安装等功能。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境
Java并发迷宫:同步的魔法与死锁的诅咒
在Java并发编程中,合理使用同步机制可以确保线程安全,避免数据不一致的问题。然而,必须警惕死锁的出现,采取适当的预防措施。通过理解同步的原理和死锁的成因,并应用有效的设计和编码实践,可以构建出高效、健壮的多线程应用程序。
49 21
课时6:Java编程起步
课时6:Java编程起步,主讲人李兴华。课程摘要:介绍Java编程的第一个程序“Hello World”,讲解如何使用记事本或EditPlus编写、保存和编译Java源代码(*.java文件),并解释类定义、主方法(public static void main)及屏幕打印(System.out.println)。强调类名与文件名一致的重要性,以及Java程序的编译和执行过程。通过实例演示,帮助初学者掌握Java编程的基本步骤和常见问题。
【YashanDB 知识库】kettle 同步大表提示 java 内存溢出
【问题分类】数据导入导出 【关键字】数据同步,kettle,数据迁移,java 内存溢出 【问题描述】kettle 同步大表提示 ERROR:could not create the java virtual machine! 【问题原因分析】java 内存溢出 【解决/规避方法】 ①增加 JVM 的堆内存大小。编辑 Spoon.bat,增加堆大小到 2GB,如: if "%PENTAHO_DI_JAVA_OPTIONS%"=="" set PENTAHO_DI_JAVA_OPTIONS="-Xms512m" "-Xmx512m" "-XX:MaxPermSize=256m" "-
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
115 5
Java 并发编程——volatile 关键字解析
|
4月前
|
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
337 2
Java并发编程 - 线程不安全类 & 同步/并发容器之简介
Java并发编程 - 线程不安全类 & 同步/并发容器之简介
135 0
Java并发编程 - 线程不安全类 & 同步/并发容器之简介
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等