• 多线程系列四:AQS-AbstractQueuedSynchronizer


    什么是AbstractQueuedSynchronizer?为什么我们要分析它? 

    AbstractQueuedSynchronizer(AQS):抽象队列同步器

    抽象队列同步器原理:当多个线程去获取锁的时候,如果获取锁失败了,获取锁失败的当前线程就会被打包成一个node节点放入同步队列里面使用LockSuportpark方法阻塞起来,如果有线程释放了锁,放入同步队列的线程就会被LockSupportunpark方法唤醒再次去获取锁,如果获取锁又失败了就再次打包成node节点放入同步队列,这样不断的循环直到拿到锁

    为什么要分析AQS:因为线程都是基于AQS实现的

    AQS的基本使用方法

    抽象队列同步器的主要使用方式是继承,子类通过继承抽象队列同步器并实现它的抽象方法来管理线程状态。可重写的方法有:

    tryAcquire  独占锁获取

    tryRelease   独占锁释放

    tryAcquireShared  共享锁获取

    tryReleaseShared  共享锁释放

    isHeldExclusively 快速判断锁是不是被线程独占

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

    模板方法设计模式举例:

    对同步状态进行更改,这时就需要使用同步器提供的3个方法

    getState() 获取同步状态

    setState 设置同步状态

    compareAndSetState  原子的设置同步状态来进行操作,是线程安全的

    用AQS实现自己的一个独占锁

    独占锁代码:

     1 package com.lgs.aqs;
     2 
     3 import java.util.concurrent.TimeUnit;
     4 import java.util.concurrent.locks.AbstractQueuedSynchronizer;
     5 import java.util.concurrent.locks.Condition;
     6 import java.util.concurrent.locks.Lock;
     7 
     8 // 用AQS实现自己的一个独占锁
     9 public class SingleLock implements Lock {
    10 
    11     // 在内部类里面实现AQS
    12     static class Sync extends AbstractQueuedSynchronizer{
    13         
    14         // 独占锁获取
    15         public boolean tryAcquire(int arg) {
    16             // 期望没有线程拿到独占锁,把当前线程状态由0改到1拿到独占锁
    17             // 获取独占锁时存在多线程获取的情况 存在线程安全问题 所以用compareAndSetState
    18             if (compareAndSetState(0, 1)) {
    19                 // 如果改成功就把当前线程设置进去
    20                 // setExclusiveOwnerThread的意思是如果是独占锁就把当自己的线程传进去
    21                 // 表示由自己的线程拿到独占的锁
    22                 setExclusiveOwnerThread(Thread.currentThread());
    23                 return true;
    24             }
    25             return false;
    26         }
    27         
    28         // 独占锁释放
    29         public boolean tryRelease(int arg) {
    30             // 置为null 表示当前线程已经不是占据锁的线程
    31             setExclusiveOwnerThread(null);
    32             // 修改独占锁状态0,表示独占删不被当前线程占用了
    33             // 释放时不存在多线程释放 不存在线程安全问题 所以用setState
    34             setState(0);
    35             return false;
    36             
    37         }
    38         
    39         // 快速判断独占锁是不是被线程独占
    40         public boolean isHeldExclusively() {
    41             // 如果等于1就表示被线程独占
    42             return getState() == 1;
    43         }
    44         
    45         // 
    46         Condition newCondition() {
    47             return new ConditionObject();
    48         }
    49     }
    50     
    51     private final Sync sync = new Sync();
    52     
    53     @Override
    54     public void lock() {
    55         // 获取锁 1表示当前线程获取的是独占锁
    56         sync.acquire(1);
    57     }
    58 
    59     @Override
    60     public void lockInterruptibly() throws InterruptedException {
    61         // 可中断获取锁
    62         sync.acquireInterruptibly(1);
    63         
    64     }
    65 
    66     @Override
    67     public Condition newCondition() {
    68         return sync.newCondition();
    69     }
    70 
    71     @Override
    72     public boolean tryLock() {
    73         // 尝试获取锁
    74         return sync.tryAcquire(1);
    75     }
    76 
    77     @Override
    78     public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    79          // 超时获取锁
    80          return sync.tryAcquireNanos(1, unit.toNanos(time));
    81     }
    82 
    83     @Override
    84     public void unlock() {
    85         // 释放锁
    86         sync.release(1);
    87         
    88     }
    89 
    90 }

    测试自己的独占锁:

     1 package com.lgs.aqs;
     2 
     3 import java.util.concurrent.locks.Lock;
     4 
     5 // 测试自己写的锁
     6 public class TestMyLock {
     7 
     8     public void test() {
     9         final Lock lock = new SingleLock();
    10         class Worker extends Thread {
    11             public void run() {
    12                 while (true) {
    13                     lock.lock();
    14                     try {
    15                         Thread.sleep(1000);
    16                         System.out.println(Thread.currentThread().getName());
    17                         Thread.sleep(1000);
    18                     } catch (InterruptedException e) {
    19                         e.printStackTrace();
    20                     } finally {
    21                         lock.unlock();
    22                     }
    23                     try {
    24                         Thread.sleep(2000);
    25                     } catch (InterruptedException e) {
    26                         e.printStackTrace();
    27                     }
    28                 }
    29             }
    30         }
    31         // 启动10个子线程
    32         for (int i = 0; i < 10; i++) {
    33             Worker w = new Worker();
    34             // w.setDaemon(true);
    35             w.start();
    36         }
    37         // 主线程每隔1秒换行
    38         for (int i = 0; i < 10; i++) {
    39             try {
    40                 Thread.sleep(1000);
    41             } catch (InterruptedException e) {
    42                 e.printStackTrace();
    43             }
    44             System.out.println();
    45         }
    46     }
    47 
    48     public static void main(String[] args) {
    49         TestMyLock testMyLock = new TestMyLock();
    50         testMyLock.test();
    51     }
    52 }

    什么是LockSupport

    LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也成为构建同步组件的基础工具。

    LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。

    简单地说:LockSupport就是用来和AQS配合阻塞和唤醒线程的

    同步队列

    抽象队列同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理

    当前线程获取锁失败时,抽象队列同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列。同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会成为节点加入该队列的尾部。

    锁的可重入

    当一个线程获得锁之后,再次进入同步快任然能获取到锁执行,实现原理是先判断当前线程是不是已经获取到锁的线程,如果是就把状态加1;释放锁的时候拿了几次锁就要释放几次

    如果没有实现锁的可重入则会出现一个线程拿到了锁之后再次获取锁,这样就会自己等待自己出现死锁

    公平锁和非公平锁

    公平锁:一个线程去竞争锁的时候先看前面有没有其他线程在等待获取锁,如果有就不参与

    非公平锁:一个线程去获取锁的时候不管前面有没有线程在等待获取锁都有参与竞争

    ReentrantReadWriteLock的实现原理

    读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键。如果在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,读写锁将变量切分成了两个部分,高16位表示读,低16位表示写。

    读状态是所有线程获取读锁次数的总和,而每个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由线程自身维护。

    Condition的实现原理

    condition是等待通知机制的另一种方式

    等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。

    一个Condition包含一个等待队列,新增节点只需要将原有的尾节点nextWaiter指向它,并且更新尾节点即可。

    调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。

    调用该方法的前置条件是当前线程必须获取了锁,可以看到signal()方法进行了isHeldExclusively()检查,也就是当前线程必须是获取了锁的线程。接着获取等待队列的首节点,将其移动到同步队列并使用LockSupport唤醒节点中的线程。

    通过调用同步器的enq(Node node)方法,等待队列中的头节点线程安全地移动到同步队列。

    Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程。

  • 相关阅读:
    使用Power Query从Web页面获取图像到Power BI报告中
    视频 |【2019】Power BI 8月产品功能更新讲解
    【2019】微软Power BI 每月功能更新系列——Power BI 8月版本功能完整解读
    视频 |【2019】Power BI 7月产品功能更新讲解
    2019 年 BI 平台 Top 14
    2016 黑客必备的Android应用都有哪些?
    J2EE完全手册(二)
    JavaBean ,Enterprise Bean(EJB), 三种Bean, 以及POJO
    J2EE完全手册(一)
    J2EE简介
  • 原文地址:https://www.cnblogs.com/leeSmall/p/8323746.html
Copyright © 2020-2023  润新知