上篇文章已经对多线程有个初步的认识了,此次咱们来看看Java的Lock锁,主要有如下知识点:java
AQS
ReentrantLock
ReentrantReadWriteLock
Lock和synchronized的选择
在学习Lock锁以前,咱们先来看看什么是AQS?算法
ReadWriteLock
。注意:ReentrantLock不是AQS的子类,其内部类Sync才是AQS的子类。
AQS维护了一个volatile int
类型的state
变量,用来表示当前同步状态。
volatile虽然不能保证操做的原子性,可是保证了当前变量state的可见性。多线程
compareAndSetState
compareAndSetState用来修改state状态,它是一个原子操做,底层实际上是调用系统的CAS算法,有关CAS可移步:CAS框架
protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
acquire
acquire(int arg) 以独占方式获取资源,若是获取到资源,线程直接返回,不然进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。函数
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
若是tryAcquire(int)
方法返回true,则acquire直接返回,不然当前线程须要进入队列进行排队。addWaiter()
将该线程加入等待队列的尾部,并标记为独占模式;学习
学习ReentrantLock以前先来看看它实现的Lock接口ui
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
ReentrantLock
,意思是"可重入锁
",线程能够重复地得到已经持有的锁。 ReentrantLock是惟一实现了Lock接口的类。接下来咱们来看看有关源码:this
ReentrantLock实现了三个内部类,分别是Sync、NonfairSync和FairSync。spa
abstract static class Sync extends AbstractQueuedSynchronizer static final class NonfairSync extends Sync static final class FairSync extends Sync
这些内部类都是AQS的子类,这就印证了咱们以前所说的:AQS是ReentrantLock的基础,AQS是构建锁的框架.线程
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
默认实现的是非公平锁,传入true表示使用公平锁。
lock
方法非公平锁
的lock方法加锁流程
首先会经过CAS
方法,尝试将当前的AQS中的State
字段改为从0改为1,若是修改为功的话,说明原来的状态是0,并无线程占用锁,并且成功的获取了锁,只须要调用setExclusiveOwnerThread
函将当前线程设置成持有锁的线程便可。不然,CAS
操做失败以后,和普通锁同样,调用父类AQS的acquire(1)
函数尝试获取锁。
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() { if (compareAndSetState(0, 1))//尝试获取锁 setExclusiveOwnerThread(Thread.currentThread()); else //获取失败则调用AQS的acquire方法 acquire(1); }
而在AQS的acquire(1)
函数中,会判断tryAcquire(1)
以及acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
,若是尝试获取失败而且添加队列成功的话,那么就会调用selfInterrupt
函数中断线程执行,说明已经加入到了AQS的队列中。
注意:AQS的tryAcquire(1)
是由子类Sync(也就是ReentrantLockd的静态内部类)本身实现的,也就是用到了模板方法,接下来咱们去看看子类的实现。
tryAcquire
是在NonfairSync
类中实现的,其中调用了nonfairTryAcquire
函数。
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) {//获取当前线程状态 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) {//可重入锁 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
在nonfairTryAcquire
函数中,会尝试让当前线程去获取锁:
CAS
操做,将当前的状态设置成acquires
,若是设置成功了的话,那么则将当前线程设置成锁持有的线程,而且返回true,表示获取成功。0
的话,说明已经有线程持有锁,则判断当前线程与持有锁的线程是否相同,若是相同的话,则将当前的状态加上acquires从新将状态设置,而且返回true,这也就是重入锁
的缘由。源码参考:ReentrantLock中的NonfairSync加锁流程
咱们知道synchronized内置锁和ReentrantLock都是互斥锁
(一次只能有一个线程进入到临界区(被锁定的区域))
而ReentrantReadWriteLock是一个读写锁
:
通常来讲:咱们大多数都是读取数据得多,修改数据得少。因此这个读写锁在这种场景下就颇有用了!
ReentrantReadWriteLock
实现了ReadWriteLock
接口.
接口只有两个方法,一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操做分开,分红2个锁来分配给线程,从而使得多个线程能够同时进行读操做
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); }
和ReentrantLock相比,ReentrantReadWriteLock多了ReadLock
和WriteLock
两个内部类。
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { private final ReentrantReadWriteLock.ReadLock readerLock; private final ReentrantReadWriteLock.WriteLock writerLock;
abstract static class Sync extends AbstractQueuedSynchronizer { static final int SHARED_SHIFT = 16;// 高16位为读锁,低16位为写锁 static final int SHARED_UNIT = (1 << SHARED_SHIFT); static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; static int sharedCount(int c) { return c >>> SHARED_SHIFT; } static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
读写锁对于同步状态的实现是将变量切割成两部分,高16位表示读,低16位表示写。
看个实际例子
class czy{ private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); ........ public void read(Thread thread) { rwl.readLock().lock(); try { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1) { System.out.println(thread.getName()+"正在进行读操做"); } System.out.println(thread.getName()+"读操做完毕"); } finally { rwl.readLock().unlock(); } } }
Lock和synchronized的选择
总结来讲,Lock和synchronized有如下几点不一样:
1)Lock是一个接口,而synchronized
是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,所以不会致使死锁现象发生;
而Lock在发生异常时,若是没有主动经过unLock()去释放锁,则极可能形成死锁现象,所以使用Lock时须要在finally块中释放锁;
3)Lock可让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不可以响应中断;
4)经过Lock能够知道有没有成功获取锁,而synchronized却没法办到。
5)Lock能够提升多个线程进行读操做的效率。
有关Lock锁的知识点就到这里,若是想了解更多请参考下面连接。