深入分析Lock(一)

     我们不仅仅可以通过synchronized来达到锁的目的,Java还 提供了很多java语言级别的锁,其接口为java.uti.concurrent.Lock.如图所示


上图是jdk1.8

       首先来说说一下readLock和writeLock,他们都是reentrantreadwritelock中的public statIc 类型的内部类,很多时候是先定义一个reentrantreadwritelock对象,然后再通过writeLock()和readLock()来获取读锁和写锁,所以我们通常把这个叫做读写锁。

     ReentrantLock最基本的两个操作就是lock()和unlock()方法。通过lock可以实现对临界资源的同步,Lock的实现出了基于CAS自旋之外,还有一个就是AQS机制。

    CAS,Compare And Set,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。顺便提一下,同步异步,阻塞和非阻塞的区别,例子如下,知乎大神总结如下

1 同步和异步

   同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由*调用者*主动等待这个*调用*的结果。

而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。

典型的异步编程模型比如Node.js

举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

2阻塞和非阻塞

 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

还是上面的例子,
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

 关于同步异步阻塞和非阻塞,引用自http://www.zhihu.com/question/19732473,知乎用户段子手;

 那么接下的重点是AQS机制

   AQS全程AbstractQueneSynchronizer,这是一个提供一系列的公共的方法让子类来调用抽象类。要看懂这个类似乎很麻烦,我们就先从ReentrantLock看起吧

  

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

    public ReentrantLock(boolean flag)
    {
        sync = ((Sync) (flag ? ((Sync) (new FairSync())) : ((Sync) (new NonfairSync()))));
    }


上面是ReentrantLock的两个构造方法,默认sync的实现类是NonFaidSync实现类。另一个是FairSync,无论使用哪一个都能实现排它锁,只是说内部原理有点区别,这里以NonfairSync入手,来看一下它的lock方法是怎么样实现的额。

  

  final void lock()
        {
            if(compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

  protected final void setExclusiveOwnerThread(Thread thread)
    {
        exclusiveOwnerThread = thread;
    }

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


lock方法首先通过CAS尝试将状态从0修改为1,如果直接修改成功的话,那么久会把线程的owner修改为当前线程,这是一种理想情况,如果并发粒度适当的话没那么这也是种乐观情况。

     如果说上一个动作未获成功的话,那么会间接调用acquire(1)来继续操作,首先会调用tryAcquire方法

 

  protected final boolean tryAcquire(int i)
        {
            Thread thread = Thread.currentThread();
            int j = getState();
            if(j == 0)
            {
                if(!hasQueuedPredecessors() && compareAndSetState(0, i))
                {
                    setExclusiveOwnerThread(thread);
                    return true;
                }
            } else
            if(thread == getExclusiveOwnerThread())
            {
                int k = j + i;
                if(k < 0)
                {
                    throw new Error("Maximum lock count exceeded");
                } else
                {
                    setState(k);
                    return true;
                }
            }
            return false;
        }


  在该方法当中我们首先会得到当前状态state的值,通过getState()方法,这个state字段是volatile修饰的,volatile这个关键字最重要的两个特点,是防止指令重排序和保证多线程中的共享变量是始终可见的(但是并不能保证volatile引用对象内部的属性是完全可见的)。正是因为volatile这两个关键特点,结合读写锁的currentHashMap才将hashTable给淘汰掉。

    这个state值在我看来可以被认为是一个起一个类似于版本号的作用的值。

   如果state值为0,首先先检查AbstractQueuedSynchronizer中的hasQueuedPredecessors()方法,来判断当前队列没有其他线程。当然一定要配出当前线程,有可能是当前线程又一次进入。

 public final boolean hasQueuedPredecessors()
    {
        Node node = tail;
        Node node1 = head;
        Node node2;
        return node1 != node && ((node2 = node1.next) == null || node2.thread != Thread.currentThread());
    }
  上面的hasQueuedPredecessors()方法是用来判断是否有除当前线程之外的线程正在占用着lock;

   同时进行CAS操作,修改state值;设置lock的占有者为当前线程;并且返回true; 

   当state值不为0,首先查看当前占用lock的线程是不是当前,如果是当前线程的,计算新的state值并且保存返回true,如果state值异常的话,抛出一个错误;其他的情况下直接返回false;

  那么接下来的话,如果再次的tryAcquire没有成功的话,会进入addWaiter()方法,如果tryAcquire成功的话,那么久愉快的ok了;

  addWaiter()在下一篇里面介绍,同时我还会捎带手介绍下condition,在介绍完addWaiter()方法的最后我再去从另一个稍微全面的视角来看一下Lock的整体工作机制和原理。

 

 

 



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值