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

目录
相关文章
|
18天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
21 0
|
20天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
4天前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
32 12
|
17天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
17天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
40 3
|
22天前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
36 2
|
23天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
55 1
|
存储 算法 安全
【Java 数据结构及算法实战】系列 014:Java队列08——数组实现的双端队列ArrayDeque
【Java 数据结构及算法实战】系列 014:Java队列08——数组实现的双端队列ArrayDeque
173 0
【Java 数据结构及算法实战】系列 014:Java队列08——数组实现的双端队列ArrayDeque
|
存储 算法 安全
【Java数据结构及算法实战】系列012:Java队列06——数组实现的优先级阻塞队列PriorityBlockingQueue
【Java数据结构及算法实战】系列012:Java队列06——数组实现的优先级阻塞队列PriorityBlockingQueue
144 0
|
存储 算法 安全
【Java数据结构及算法实战】系列009:Java队列03——数组实现的阻塞队列ArrayBlockingQueue
顾名思义,ArrayBlockingQueue是基于数组实现的有界阻塞队列。该队列对元素进行FIFO排序。队列的首元素是在该队列中驻留时间最长的元素。队列的尾部是在该队列中停留时间最短的元素。新的元素被插入到队列的尾部,队列检索操作获取队列头部的元素。
146 0