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
独占式同步状态(锁)获取流程

目录
相关文章
|
23天前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
55 5
|
27天前
|
Java 程序员 调度
【JAVA 并发秘籍】进程、线程、协程:揭秘并发编程的终极武器!
【8月更文挑战第25天】本文以问答形式深入探讨了并发编程中的核心概念——进程、线程与协程,并详细介绍了它们在Java中的应用。文章不仅解释了每个概念的基本原理及其差异,还提供了实用的示例代码,帮助读者理解如何在Java环境中实现这些并发机制。无论你是希望提高编程技能的专业开发者,还是准备技术面试的求职者,都能从本文获得有价值的见解。
33 1
|
10天前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
12天前
|
安全 Java 测试技术
掌握Java的并发编程:解锁高效代码的秘密
在Java的世界里,并发编程就像是一场精妙的舞蹈,需要精准的步伐和和谐的节奏。本文将带你走进Java并发的世界,从基础概念到高级技巧,一步步揭示如何编写高效、稳定的并发代码。让我们一起探索线程池的奥秘、同步机制的智慧,以及避免常见陷阱的策略。
|
23天前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
79 5
|
23天前
|
Java 程序员
从0到1,手把手教你玩转Java多线程同步!
从0到1,手把手教你玩转Java多线程同步!
20 3
|
23天前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
Java多线程同步实战:从synchronized到Lock的进化之路!
83 1
|
28天前
|
安全 Java 编译器
深入Java内存模型:解锁并发编程的秘密
【8月更文挑战第24天】在Java的世界,内存模型是支撑并发编程的基石。本文将深入浅出地探讨Java内存模型(JMM)的核心概念、工作原理及其对高效并发策略的影响。我们将通过实际代码示例,揭示如何利用JMM来设计高性能的并发应用,并避免常见的并发陷阱。无论你是Java新手还是资深开发者,这篇文章都将为你打开并发编程的新视角。
31 2
|
28天前
|
安全 Java
Java 中同步 ArrayList 的详细指南
【8月更文挑战第23天】
39 1
|
20天前
|
C# 开发者 数据处理
WPF开发者必备秘籍:深度解析数据网格最佳实践,轻松玩转数据展示与编辑大揭秘!
【8月更文挑战第31天】数据网格控件是WPF应用程序中展示和编辑数据的关键组件,提供排序、筛选等功能,显著提升用户体验。本文探讨WPF中数据网格的最佳实践,通过DevExpress DataGrid示例介绍其集成方法,包括添加引用、定义数据模型及XAML配置。通过遵循数据绑定、性能优化、自定义列等最佳实践,可大幅提升数据处理效率和用户体验。
40 0