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

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

章节目录

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

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

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

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

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

队列同步器定义:

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

队列同步器使用方式

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

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

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

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

2.1 模板方法模式

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

2.2 重写同步器指定的方法

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

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();
    }
}

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

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

3 队列同步器的实现分析

3.1 同步队列数据结构

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

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

img_893ee1773804ef82128d84607a1a3ee4.png
同步队列基本结构

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

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

3.3 成功获取同步状态

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

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

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

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

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

目录
相关文章
|
7月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
344 0
|
10月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
400 1
|
11月前
|
Java 数据库
【YashanDB知识库】kettle同步大表提示java内存溢出
在数据导入导出场景中,使用Kettle进行大表数据同步时出现“ERROR:could not create the java virtual machine!”问题,原因为Java内存溢出。解决方法包括:1) 编辑Spoon.bat增大JVM堆内存至2GB;2) 优化Kettle转换流程,如调整批量大小、精简步骤;3) 合理设置并行线程数(PARALLELISM参数)。此问题影响所有版本,需根据实际需求调整相关参数以避免内存不足。
|
安全 Java 开发者
Java并发迷宫:同步的魔法与死锁的诅咒
在Java并发编程中,合理使用同步机制可以确保线程安全,避免数据不一致的问题。然而,必须警惕死锁的出现,采取适当的预防措施。通过理解同步的原理和死锁的成因,并应用有效的设计和编码实践,可以构建出高效、健壮的多线程应用程序。
235 21
|
Java Shell 数据库
【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
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
770 12
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
187 0
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
897 6
|
设计模式 安全 Java
Java 多线程并发编程
Java多线程并发编程是指在Java程序中使用多个线程同时执行,以提高程序的运行效率和响应速度。通过合理管理和调度线程,可以充分利用多核处理器资源,实现高效的任务处理。本内容将介绍Java多线程的基础概念、实现方式及常见问题解决方法。
440 1
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
230 2