本文共 2319 字,大约阅读时间需要 7 分钟。
其实之前博客中就已经提到了JUC,今天详细的透析下JUC的锁机制和锁原理。
在JDK1.1~1.2 的时候 只有synchronize锁,此时的锁只是一个重量级锁,所谓的重量级锁我个人理解就是线程在使用资源时需要通过OS线程队列进行调度,只有在获取锁(没有其他线程占用锁)的情况下才能进行。这类锁一般具有排他性,所以也叫悲观锁。
而早期的synchronize不管你是否是多线程竞争关系,都会同样的采取重量级锁这一套。这样再简单的线程操作中,就很影响效率。在JDK 1.5 之后推出了JUC包,这个包提供了很多原子类,用于保证多线程同步。
JUC的锁机制和锁原理
JUC的提供了很多五花八门的锁,简单介绍下: ReentrantLock:和synchronized具有差不多的语义,独占锁,同时只有一个线程能获得锁。 ReentrantReadWriteLock:读写锁。读允许共享,写独占。适用于读频繁的场景。 CountDownLatch:闭锁。使用场景类似比赛鸣枪,在没有鸣枪之前所有的运动员(线程)都必须等待。说白了就是使用于一个或多个线程等待某一个条件成立了,触发运行。 Semaphore:信号量。使用场景类似通行证,通行证数量有限,拿到证的才能通行。 CyclicBarrier:周期障碍。语义上和CountDownLatch有点类似,只有几个线程打到一个共同的状态之后,触发后续动作继续。不同在于1.达到共同状态后,可以指定一个后续触发的线程对象。2.周期性意味着可以周期运行。 FutureTask:带有返回值的异步执行。 等等这些锁的都会在不同场景中被使用,这些同步措施可以统称为sync 同步器框架。
以上各种锁的使用场景后面有机会在介绍。
JUC锁原理(底层)
老样子还是举例说明:JUC的实现类ReentrantLock锁。 ReentrantLock锁 和 synchronize锁的区别在之前就已经说过了,现在重点说下JUC的锁机制和锁原理。先简单说下JUC底层实现原理是CAS和AQS,CAS在上面文章已经说过了,是一种自旋锁的实现方式。简单看下ReentrantLock实现代码:
final Lock lock = new ReentrantLock(false); public void study() throws InterruptedException { for (int i = 0; i <2 ; i++) { try { lock.lock(); // 等待5秒钟拿不到锁就返回false System.out.println(Thread.currentThread().getName()+":我正在学习"); //Thread.sleep(2000); }catch (Exception e){ }finally { lock.unlock(); } } }
这里用到了ReentrantLock锁对线程进行了同步操作。那么我们就看下这个lock怎么处理的?
进到lock方法实现类去看看: 这里就是Sync同步器类的调用。继续进去看下: 进去之后我们可以看到这里面有两个实现类:FairSync 和 NonfairSync 这两个分表就是公平锁和非公平锁的实现类。进到默认的非公平锁类: 这代码什么意思呢?if (compareAndSetState(0, 1))
判断是否设置锁成功,看下怎么判断的?
这里是不是就看到熟悉的字样了compareAndSwapInt (CAS)。如果成功了就把该线程设置为当前线程(线程暂用),如果不成就进行 acquire(1)操作(好像有点废话了),那么就核心看下 acquire(1);内部实现:
这里我们可以看到if(;;)操作,就是无限循环,这里什么意思呢?就是说如果lock设置锁没有成功,那么就无限的循环去尝试设定锁,直到成果为止。看到这是不是明白了JUC底层原理是CAS了。AQS实现原理
AQS是什么呢? 以上说的JUC包下所有的实现都是继承AQS(AbstractOwnableSynchronizer)类实现。 上面说的ReentrantLock的lock刚才看到了,是基于NonfairSync 。 那我们再看下这个类继承关系:那么我们先看看AQS是怎么实现的呢?
看这张图: AQS实现了一个框架实现原理CAS+voalitle,框架中定义基础属性state(一个锁状态标识,根据不同的锁机制定位含义)。还实现了一个双向链表队列框架(CLH)。关于这个队列的所有操作都是关于CAS实现。当一个线程来的时候会把自己做成一个node去排队,当有很多线程需要排队竞争队列tail(尾巴)位置时,就是用到了CAS实现。
然后我们通过源码在一起看下JUC的AQS:
再看下setExclusiveOwnerThread: 我们可以看到占用锁的操作正是AbstractOwnableSynchronizer。然后我们再看下如果没有设置锁成功呢?
刚才我们看了acquire方法,无限的循环去尝试设置。
AQS正是利用CAS将node加到双向链表队列中。 关于JUC,还有很多同步工具:转载地址:http://ljhof.baihongyu.com/