并发编程之锁的原理--java并发编程之美(四)

LockSupport工具类的常用方法及作用

该类的主要作用是挂起和唤醒线程,该工具类是创建锁和其它同步类的基础。

void park()
  • 介绍:调用park方法的线程如果已经拿到与LockSupport关联的许可证,则调用LockSupport.park()方法会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。
  • 调用park方法时最好使用循环条件判断方式,因为当其他线程以当前线程做为unpark方法的参数,或者其它线程调用了interrupted方法,当前阻塞线程都会返回。
  • park()方法阻塞的线程被其它线程中断不会抛出中断异常

例子:可以发现在主线程中调用LockSupport.part()方法后,主线程受到了阻塞。
在这里插入图片描述

void unpark(Thread thread)方法
  • 介绍:给传入的线程 颁发LockSupport的许可证
  • 如果传入的线程没有许可证,则给他一个许可证,如果它还处于阻塞线程状态,则唤醒它。
  • 拥有了许可证的线程,在执行park() 方法之后,会立刻返回,不会阻塞了。
void parkNanos(long nanos)方法

这个与park方法类似,区别在于这个可以传入时间,表示从当前时间开始计算,在nanos时间后,即时阻塞也可以被返回。

void park(Object blocker)方法(推荐使用,可查阻塞线程)

当线程调用这个park方法,因没有许可证而被阻塞时,这个blocker会被记录到该线程内部。这个时候,使用诊断工具可以观察线程被阻塞的原因。
blocker可以被设置为this
jstack pid 命令查看线程堆栈时可以看到更多有关阻塞对象this的信息

void parkUntil(Object blocker,long deadline)

与parkNanos方法不同的是,等到指定时间点,返回阻塞线程

那么wait和notify呢?与park,unpark的比较

wait和notify是Object的方法,park和unpark是JUC.locks包下的方法
wait和notify需要配合同步代码块使用,必须要先获取监视锁,但是park和unpark方法并不需要,可以在任何地方使用。
wait和notify方法需要严格控制执行顺序。
park支持超时等待立刻避免阻塞,wait的超时等待只能够重新再去竞争锁。
unpark支持唤醒指定线程,notify不支持。

锁的底层支持–AQS(抽象同步队列)

  • AQS是一个双端队列,维持了一个单一的状态的信息state。(线程的同步的关键是对state的操作)对于可重入锁ReentrantLock来说,可以表示获取锁的可重入次数。对于读写锁ReantrantReadWriteLock来说,state的高16位表示获取读锁的次数,低16位表示获取到写锁的可重入次数。对于Semaphore来说,表示信号量。对于CountDownlatch来说,用来表示计数器当前的值
  • 队列元素是一个Node,该Node标记了该线程获取的共享资源还是独占资源,同时该Node中还记录了当前线程的等待状态waitStatus,是被取消了,还是需要被唤醒,还是在队列里等待,还是释放资源需要通知其他节点。
  • AQS内部还维护了一个内部类,用来结合锁实现线程同步。可以由该类,直接访问AQS对象的内部的变量

独占锁ReentrantLock的介绍

类图结构
在这里插入图片描述
结合 ReentrantLock() 的构造器

    public ReentrantLock() {
        sync = new NonfairSync();
    }

可以发现,我们创建ReentrantLock锁,最终创建的还是一个继承AQS的Sync类的子类。即最终还是通过AQS来实现的同步操作。

在ReentrantLock中,AQS对于它来说,里面的state就代表了锁的可重入次数。默认情况下,state值为0,表示当前没有被任何线程持有,当第一个线程第一次获取锁时,会使用CAS将state设置为1,如果在未释放锁的情况下,再次获取锁,则将state再次+1变为2,如果释放锁,则-1,如果最后状态值为0,则表示当前线程释放该锁。

void lock()方法

可以发现lock的方法交给了sync去实现,而sync则交给了其两个子类的lock实现方法。一个是公平锁,一个是非公平锁。
在这里插入图片描述
先来看一下非公平锁的lock实现

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        final void lock() {
        	//cas判断,如果当前AQS中的state状态为0,则将其改为1
            if (compareAndSetState(0, 1))
            //并将当前的线程,设置为锁的持有者
                setExclusiveOwnerThread(Thread.currentThread());
            else
            //如果当前线程想要获取锁,获取不到,则会执行acquire方法
            //该方法会去执行tryAcquire(),然后可能就将线程放入阻塞队列
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

由于tryAcquire,AQS并没有提供这个方法,所以ReentrantLock重写了这个方法。下面是非公平类重写的这个非公平方法

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取AQS的状态值
            int c = getState();
            if (c == 0) {
            //如果状态值为0,则同样是进行一个cas设置state的操作
            //让当前线程成为锁的拥有者,并把状态值改为1
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果发现状态值不为0,即该锁被别的线程持有着
            //则返回false,然后该线程就在acquire的代码中被放入阻塞队列了
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                //先判断当前的可重入次数,如果小于0,则抛出异常
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

接下来看看公平锁的类的实现

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

        final void lock() {
        //直接调用的acquire方法,然后就是公平类重写的tryAcquire方法
            acquire(1);
        }

下面是公平类里重写的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;
        }

ReentrantReadWriteLock的介绍

ReentrantLock是独占锁,只能由一个线程占用一个锁,适用于写多读少的场景。但实际情况中,总有需要用到读多写少的场景,所以ReentrantReadWriteLock应运而生。它采用读写分离的策略,使得多个线程可以同时获取锁。

类图结构
在这里插入图片描述
由上述类图结构,可以发现,读写锁内部维护了一个ReadLock和一个WriteLock,它们依赖Sync实现具体功能。而Sync继承自AQS,并且也提供了公平和非公平的实现。但AQS中只维护了一个state状态,而读写锁需要维护两个状态(读和写)那么一个state怎么表示呢?利用state的高16位表示读状态(即获取到读锁的次数),低16位表示写状态(即获取到写锁的可重入次数)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值