wittyResry / myIssue

My issue mark down^_^ 欢迎吐槽,讨论~~
https://github.com/wittyResry/myIssue/issues
The Unlicense
5 stars 1 forks source link

并发底层实现之AQS(AbstractQueuedSynchronizer) #107

Open wittyResry opened 4 years ago

wittyResry commented 4 years ago

一、首先需要了解LockSupport.park和LockSupport.unpark(thread)

package com.mytest.lock;

import java.util.concurrent.locks.LockSupport;

import org.junit.Test;

import com.mytest.common.utils.LogUtil;

/**
 * LockSupport在AQS中用到,这里探究下其原理
 * 使用场景:
 * 1. unpark函数可以先于park调用。比如线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时,它发现已经有“许可”了,那么它会马上再继续运行
 *
 * @author liqingyu
 * @since 2019/03/07
 */
public class LockSupportTest {
    /**
     * 由于获取不到锁,线程将一直运行,主线程一直处于阻塞状态
     * 因为许可默认是被占用的,调用park()时获取不到许可,所以进入阻塞状态
     */
    @Test
    public void test01() {
        LogUtil.digest("开始执行LockSupport.park()");
        LockSupport.park();//被阻塞的线程不响应中断
        LogUtil.digest("线程获取不到许可,阻塞");
    }

    /**
     * 先释放许可,再获取许可
     *LockSupport.park() 获取去壳不可重入
     */
    @Test
    public void test02() {
        Thread thread = Thread.currentThread();
        LockSupport.unpark(thread);//先调用unpark再调用park则线程不会被挂起
        LockSupport.park();
        LockSupport.unpark(thread);//去掉这行则将一直等待下去,说明不可重入
        LockSupport.park();
    }

    /**
     * 线程如果因为调用park而阻塞的话
     * 能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException。
     */
    @Test
    public void test03() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                LogUtil.digest("开始执行thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                LogUtil.digest("开始执行park,线程进入阻塞");
                LockSupport.park();
                LogUtil.digest("响应中断,isInterrupted=" + Thread.currentThread().isInterrupted());
            }
        });
        thread.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断
        thread.interrupt();

    }

    /**
     * 上面例子的另一种写法
     */
    @Test
    public void test03_2() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                LogUtil.digest("开始执行子线程");
                while(!Thread.currentThread().isInterrupted()) {
                    LogUtil.digest("开始执行park,线程进入阻塞");
                    LockSupport.park();
                }
                LogUtil.digest("响应中断,isInterrupted=" + Thread.currentThread().isInterrupted());
            }
        });
        thread.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断
        thread.interrupt();
    }

    /**
     * 子线程响应主线程
     */
    @Test
    public void test04() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                LockSupport.park();//挂起
                System.out.println("子线程继续执行");
            }
        });
        thread.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程unpark子线程");
        LockSupport.unpark(thread);
    }
}
wittyResry commented 4 years ago

二、AQS实现原理

(1)当一个线程调用acquire(int arg),首先使用tryAcquire(arg)尝试获取独占资源,获取失败则将失败则将当前线程封装为类型为Node.EXCLUSIVE的Node节点后插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己。

public final void acquire(int arg) {
  if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
    selfInterrupt();
  }
}

(2) 当一个线程调用release(arg),首先使用tryRelease(arg)释放资源,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread)。

public final boolean release(int arg) {
  if (tryRelease(arg)) {
    Node h = head;
    if (h != null && h.waitStatus != 0) {
      unparkSuccessor(h);  
    }
    return true;
   }
   return false;
}

需要注意的是,AQS类并没有提供可用的tryAcquire和tryRelease方法,正如AQS是锁阻塞和同步器的基础框架一样,tryAcquire和tryRelease需要由具体的子类来实现。