AQS

AQS

一、多线程中同步如何实现的

wait、notify、sychronized、ReentrantLock

为什么要有锁?
保证线程同步执行。

wait要和synchronized一起使用

public class Synctest
{
    public static void main(String[] args) throws InterruptedException 
    {
        Synctest synctest = new Synctest();
        synctest.wait();
    }
}

上面的代码wait没和synchronized一起使用,就会报错,而且锁住的对象要和调用wait方法的对象是一致的,不然也会报错。

例子

public class Synctest
{

    public static void main(String[] args) throws InterruptedException {
        Synctest synctest = new Synctest();
        Synctest synctest2 = new Synctest();
        synchronized (synctest)
        {
            synctest2.wait();
        }

    }
}

正确代码

public class Synctest
{

    public static void main(String[] args) throws InterruptedException {
        Synctest synctest = new Synctest();
        synchronized (synctest)
        {
            synctest.wait();
        }

    }
}

一、ReentrantLock

1.6之前,synchronized实现同步是一个重量级的锁,因为会去调用操作系统的函数

Thread.start();方法,这个方法会去调用start0()方法,这个方法会去调用C文件启动一个线程,因为这是一个native方法。
同样的道理,synchronized也会去调用一个native方法。

1、模拟一个同步的思路

while自旋
volatile int status = 0;
void lock()
{
	while(!compareAndSet(0,1)) //比较status,如果是0,改成1,可以改成1的话返回true,这个时候,status改成了1,再有一个线程来的话,不等于0,返回false,!false=true,会一直在while循环里面。
	{
	
	}
	//2s
}
void unlock()
{
	status = 0;
}
boolean compareAndSet(int except,int newValue)
{
	//cas操作。
}

缺点:耗费cpu
改进思路:让得不到锁的线程让出cpu。

yield+自旋
volatile int status = 0;
void lock()
{
	while(!compareAndSet(0,1))
	{
		yield(); //让出cpu
	}
}
void unlock()
{
	status = 0;
}

yield也存在问题,yield方法只是当前线程让出cpu,有可能操作系统下次还是运行该线程。

sleep+自旋
volatile int status = 0;
void lock()
{
	while(!compareAndSet(0,1))
	{
		sleep(10); 
	}
}
void unlock()
{
	status = 0;
}

睡眠存在的问题:睡多久如何确定?
比如我t1线程只需要1s操作,但是你睡眠了10s。

park、unpark

park和unpark解释

park是让线程阻塞(阻塞是让出cpu),unpark是叫醒某个线程。

sleep应该也是让出cpu
LockSupport调用unpark、park底层也是调用Unsafe类的这两个native方法

//import java.util.Scanner;

import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

public class Test
{

    public static void main(String[] args)
    {
        Thread t1 = new Thread()
        {
            @Override
            public void run() 
            {
                testSync();
            }
        };

        t1.setName("t1");
        t1.start();

        try 
        {
            Thread.sleep(1000);
        } 
        catch (InterruptedException e) 
        {
            e.printStackTrace();
        }
        System.out.println("main----1");
        LockSupport.unpark(t1);
    }
    public static void testSync()
    {

        System.out.println(Thread.currentThread().getName());
        LockSupport.park();
        System.out.println("end-------------");


    }
}

2、ReentrantLock、Sync、(非)公平之间关系

ReentrantLock初始化其实就是创建一个公平锁或者非公平锁。它的lock、unlock其实就是调用公平锁或者非公平锁的lock或者unlock。

Sync继承了AQS,公平锁和非公平锁继承了Sync,ReentrantLock初始化创建的就是一个公平锁或者非公平锁,所以 ReentrantLock初始化其实就是得到了一个实现AQS的一个对象,AQS里面有一个Node内部类Node,创建了AQS也就创建了一个队列,里面放着线程、下一个结点、前面一个结点等信息。
在这里插入图片描述

ReentrantLock的lock方法会调用Sync类的lock方法,Sync是一个抽象类,继承AQS(abstract static class Sync extends AbstractQueuedSynchronizer。),Sync的lock方法是一个抽象方法,Sync有两个实现类FairSync和N onfairSync。
在这里插入图片描述
在这里插入图片描述

ReentrantLock其实初始化就是创建一个公平锁或者非公平锁,默认实现是一个非公平锁,如果参数是true,则创建一个公平锁,否则创建一个非公平锁。

3、lock代码解析

公平锁调用lock方法,会调用acquire(1);
acquire 英[əˈkwaɪə®]

static final class FairSync extends Sync 
{
     private static final long serialVersionUID = -3000897897090466540L;

     final void lock() 
     {
         acquire(1);
     }
     .......

tryAcquire方法在锁被别的线程持有的时候返回false,没被其他线程持有返回true。acquire方法发现没被其他线程持有直接return,否则立马排队。

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

 }
1、tryAcquire

下面是AQS的tryAcquire方法:

protected boolean tryAcquire(int arg) 
{
   throw new UnsupportedOperationException();
 }

公平锁的tryAcquire方法:

protected final boolean tryAcquire(int acquires) 
{
     final Thread current = Thread.currentThread();
     int c = getState();
     if (c == 0) 
     {
         if (!hasQueuedPredecessors() &&
             compareAndSetState(0, acquires)) 
         {
             setExclusiveOwnerThread(current);
             return true;
         }
     }
     else if (current == getExclusiveOwnerThread()) {
         int nextc = c + acquires;
         if (nextc < 0)
             throw new Error("Maximum lock count exceeded");
         setState(nextc);
         return true;
     }
     return false;
 }
public final boolean hasQueuedPredecessors() 
{
     // The correctness of this depends on head being initialized
     // before tail and on head.next being accurate if the current
     // thread is first in queue.
     Node t = tail; // Read fields in reverse initialization order
     Node h = head;
     Node s;
     return h != t &&
         ((s = h.next) == null || s.thread != Thread.currentThread());
 }

tryAcquire会先得到调用lock的线程,然后调用getState,返回state属性,state默认是0,if(c=0)其实就是判断锁是不是自由状态,第一次调用等于0的话即没有其他线程被锁住,此时,查看自己是否需要排队即查看队列是否是空。

compareAndSetState会调用下面的方法
在这里插入图片描述
在这里插入图片描述

2、重点总结

原来的synchronized 1.6之前不管你在交替执行还是非交替指向都会调用OS,而ReentrantLock在非交替执行的时候队列为空,直接返回,并不会调用OS,在JDK级别就能解决同步问题,只有在交替指向的时候才会调用OS中的函数。

aqs主要使用自旋、park、unpark、CAS实现

3、ReentrantLock在tryAcquire中重入锁的体现

在这里插入图片描述
else if的判断是判断当前线程和持有锁的线程是不是一个,如果就是重入锁,如果相同把c+1,因为acquires等于1,这就是ReentrantLock的重入锁实现。

4、在tryAcquire返回false时,会调用addWriter方法

在这里插入图片描述

如果多个线程并发执行的话,有线程没执行完就被抢占,tryAcquire就会return false;!false=true,后来就会执行入队操作acquireQueued(addWaiter(Node.EXCLUSIVE),arg);

在这里插入图片描述

addWaiter方法
第一次调用的时候,会调用enq
方法初始化一个结点,线程甚至为null,作为第一个结点,再把线程入队,以后再有线程阻塞的话,直接入队。

private Node addWaiter(Node mode) 
{
    Node node = new Node(Thread.currentThread(), mode); //初始化一个结点
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) 
    {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) 
        {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

第一次tail为null,head也为null,所以addWaiter会直接调用enq(node);

enq(node)代码如下
只有初始化才调用enq,第一次调的话,初始化队列入队

private Node enq(final Node node) 
{
    for (;;) 
    {
        Node t = tail;
        if (t == null)  //第一次调用的话,tail为null,结果为true。
        { // Must initialize
            if (compareAndSetHead(new Node()))  //第一次设置队列中第一个节点的时候,并没有设置线程
                tail = head;  //首尾指针都指向第一个结点
        } 
        else 
        {
            node.prev = t; //第二个节点或者说真正的第一个结点头部指向前面一个节点
            if (compareAndSetTail(t, node)) 
            {
                t.next = node;
                return t;
            }
        }
    }
}

compareAndSetHead代码,通过下面的代码设置head。
从上面代码可以看出,第一次设置队列中第一个节点的时候,并没有设置线程,可以得出,AQS队列中的第一个结点中的线程一定为空,只是初始化了一个节点,它的Thread属性为空,然后再把阻塞的线程入队。

private final boolean compareAndSetHead(Node update) 
{
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

acquireQueued代码
当队列中有其他结点的时候,当有其他结点在队列中的时候(除了Thread为null的结点),要看能不能拿到锁,因为可能在它入队的时候,。

会自旋两次,第一次刚调用acquireQueued,第二次在因为shouldParkAfterFailedAcquire第一次调用是false,所以还会再自旋一次(即调用tryAcquire),但是如果进来的线程不是第一个被阻塞的线程,就不会自旋

final boolean acquireQueued(final Node node, int arg) 
{
    boolean failed = true;
    try 
    {
        boolean interrupted = false;
        for (;;) //这个循环要注意
        {
            final Node p = node.predecessor(); //取出这次放入结点的前面一个结点
            if (p == head && tryAcquire(arg))  //判断p==head也就是判断自己是不是第一个排队的,tryAcquire就是尝试拿一下锁
            {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) //不是第一个真正的节点的话,或者是真正的第一个结点,但是有线程正在使用,这样就会判断这个if。
                interrupted = true;
        }
    } 
    finally 
    {
        if (failed)
            cancelAcquire(node);
    }
}

shouldParkAfterFailedAcquire代码

后一个节点会把前面一个节点的ws改成-1,-1表示线程正在睡眠。
为什么这个方法让多自旋一次,是因为不希望调用park

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) 
{
    int ws = pred.waitStatus;  //ws就是线程状态,初始为0
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) 
    {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do 
        {
            node.prev = pred = pred.prev;
        }
         while (pred.waitStatus > 0);
        pred.next = node;
    } 
    else 
    {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL); //Node.SIGNAL是-1,把ws改成-1
    }
    return false;  //
}

parkAndCheckInterrupt代码,
中断线程

private final boolean parkAndCheckInterrupt() 
{
    LockSupport.park(this);
    return Thread.interrupted();
}

unlock代码分析

1、unlock代码
public void unlock() 
{
    sync.release(1);
}

sync里面有队列,通过队列控制哪个线程的锁的释放。

2、release代码分析
public final boolean release(int arg) 
{
    if (tryRelease(arg)) 
    {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
tryRelease代码分析
protected final boolean tryRelease(int releases) //releases传过来为1。
{
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) 
    {
        free = true; //
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

unparkSuccessor代码

private void unparkSuccessor(Node node) {
   /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

AQS流程图

在这里插入图片描述
在这里插入图片描述

队列中如何打断

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值