并发编程 - LockSupport工具类、CLH队列锁、AQS(AbstractQueuedSynchronizer)同步队列锁

什么是LockSupport

LockSupport用于将当前线程进行阻塞,然后由其他线程进行唤醒的一个工具类,适用于A线程执行时,执行到一定位置时,需要进行阻塞,等待其他线程唤醒,与join方法有点类似。以下时Java实现代码。

public class lockSupport {
	public static void main(String[] args) {
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("start");
				LockSupport.park();
				System.out.println("end");
			}
		});
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					TimeUnit.SECONDS.sleep(3);//暂停3秒后唤醒阻塞的线程thread1
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				LockSupport.unpark(thread1);
			}
		});
		thread1.start();
		thread2.start();
	}
}

运行结果:

start
end

什么是CLH队列锁

CLH是由链表组成,每个节点由QNode组成,用于存放当前等待获取锁的线程信息。
每当一个线程进来,都将会把自身打包成一个QNode节点,存放到CLH队列尾部,QNode节点有两个域:myPred、locked。myPred指向上一个节点,不断监听上一个节点的locked是否为false(初始时为true,代表需要获取锁)。locked用于标识当前线程使用锁的状态,如何为true,代表当前线程正在使用锁或还未获取锁,当使用完毕之后,会将locked从true改为false,由下一个线程去获取该锁。

从CLH队列锁升级到AQS.Node

CLH内部结构组成

  • myPred 上一个节点的引用
  • locked 当前结点的锁状态,默认为true,为false代表释放了锁

AQS.Node内部结构组成

  • prev 上一个节点
  • next 下一个节点
  • thread当前节点保存的线程实例
  • waitStatus 当前节点的状态,1(CANCELLED) 取消获取锁,-1(SIGNAL)线程已就绪,等待锁,-2(CONDITION)等待某个条件被满足(例如等待通知signal),-3(PROPAGATE) 传播(例如当前持有读锁,那么会依次传播到所有等待获取读锁的线程)
  • SHARED 共享模式等待锁
  • EXCLUSIVE 互斥模式等待锁
  • nextWaiter 等待condition条件满足的Node节点
    PS:共享模式与互斥模式一般用于读写锁时使用,在读锁的时候,使用的SHARED ,而在写锁的时候使用的EXCLUSIVE。下面是读写锁进行lock时所采用的模式。
//写锁中的lock所调用的acquire
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//写锁采用的模式
            selfInterrupt();
    }
//读锁中的lock所调用的acquireShared -> doAcquireShared
 public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
 private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);//读锁采用的模式
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

设计模式 - > 模板模式

在不影响父类正常使用的情况下,将该类中的某些方法交给子类去实现。
比如说,在父类中有一个方法,该方法的作用是调用另一个空实现的方法,根据该方法的返回结果进行处理,如果返回true,那么执行true的方法,否则执行false的方法。所以具体是返回true还是返回false由子类去重写该方法进行实现。因此称之为模板模式。
Srping中template就是典型的模板模式

AQS(AbstractQueuedSynchronizer)

独占锁:acquire(获取锁)、release(释放锁)、tryAcquire(需要自己实现)、tryRelease(需要自己实现)、isHeldExclusively(需要自己实现)

tryAcquire:尝试拿锁 ,下面代码是由ReentrantLock中公平锁对tryAcquire的实现,可以看到,在该方法内先获取当前锁的状态,如果为0,那么采用CAS机制去修改state,如果修改成功,那么将当前线程设置为获取锁的线程,并且返回true,代表获取锁成功。否则加入队列尾部进行等待,此等待采用的是LockSupport.park()

/**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();//获取当前state状态
            if (c == 0) {//如果等于0,代表锁没有其他线程获取到
            	//hasQueuedPredecessors返回false才会执行,其关键代码为
            	/**h != t &&
            		((s = h.next) == null || s.thread != Thread.currentThread());
            		即
            		1、头=尾
            		2、头!=尾 && 头指向的下一个节点不为null 且 等于当前线程
            		只有在这种情况下,才会去尝试修改state值
            		(也就是说,判断队列是否有值,如果有值,那么排队)
            	**/
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {//尝试将0改为1,即当前线程获取锁成功
                    setExclusiveOwnerThread(current);//设置当前线程为获取锁的线程
                    return true;
                }
            }
            //如果已有锁,检测是否为当前线程持有锁
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;//对数字进行累加,实现锁的可重入,防止死锁
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//设置,因为仅当前线程可以设置,所以不采用CAS
                return true;
            }
            return false;
        }//返回true代表拿锁成功,返回false代表拿锁失败

tryRelease:尝试释放锁,下面代码是由ReentrantLock中对释放锁的实现,在调用release方法时,会优先调用tryRelease方法,来检测是否满足释放锁的条件,如果满足了释放锁的条件,那么会将锁释放,并且唤醒后面的线程,此处唤醒采用的LockSupport.unpack(n.thread)

protected final boolean tryRelease(int releases) {
			//获取state状态,然后减去releases,releases由unlock调用时传递的值为1 
			// public void unlock() { sync.release(1); }
            int c = getState() - releases;
            //判断调用释放锁线程是否为当前线程,如果不为当前线程,那么抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;//默认释放锁失败
            if (c == 0) {//如果扣减次数为0,代表可重入次数结束
                free = true;
                setExclusiveOwnerThread(null);//将当前线程设置为null
            }
            //由于调用释放锁仅为当前线程,所有此方法同一时间仅有一个线程调用,
            //因此不需要采用CAS机制就可以保证线程安全
            setState(c);
            return free;
        }

isHeldExclusively检测当前线程是否获取锁,下面代码是由ReentrantLock中对锁的持有状态检测的实现,如果当前线程持有锁,那么返回true,否则返回false

protected final boolean isHeldExclusively() {
            //根据当前设置的线程thread与当前所执行的线程进行比较,如果相等,那么即当前线程持有锁
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

共享锁:acquireShared(获取锁)、releaseShared(释放锁)、tryAcquireShared(需要自己实现)、tryReleaseShared(需要自己实现)、isHeldExclusively(需要自己实现)

Condition

是对AQS队列上进行等待通知的实现。可以帮助获取锁的线程,在执行中,可能需要等待某个条件满足时才能继续往下执行,调用await方法,将自身线程的Node节点打包到condition中并加入到尾部,直到另一个线程调用了signal方法时,才会将处于await状态的线程唤醒并从condition的头取出放入到AQS队列的尾部,进入等待获取锁,直到获取锁成功,处于await状态的线程才会被真正唤醒。(PS:进入condition队列时,对AQS队列不受影响)

提供两个常用方法:await(等待)、signal(唤醒)
await:将当前线程进入park状态。
signal:将park状态线程唤醒。

//condition中await方法
public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //将当前线程打包成一个node节点并添加到condition中
            Node node = addConditionWaiter();
            //将当前线程所持有的锁进行释放
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);//阻塞当前线程
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //通过自旋方式获取锁,不满足时进入阻塞状态
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

ReentrantReadWriteLock是如何实现互斥与可重入的呢?

从源码上看,读锁与写锁,都是使用的同一个Sync。在Sync里面,只有一个state。

由于state是一个int类型,具有32位,所以ReentrantReadWriteLock将state拆分成高位16位和低位16位,分别表示成读锁与写锁。

因此,每个线程进来后,通过计算可以得到当前是采用的共享锁还是互斥锁。
读锁(共享锁)使用时,通过ThreadLocal记录每个线程的进入次数,在归还锁的时候,对当前线程的Local进行扣减归0时,才进行释放。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值