锁的分类 可重入/不可重入锁
参考链接: 究竟什么是可重入锁
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class) 在Java中ReentrantLock和synchronized都是可重入锁,区别在于:
Synchronized是依赖于JVM实现的,而ReentrantLock是JDK实现的
ReentrantLock可以指定是公平锁还是非公平锁。而Synchronized只能是非公平锁
ReentrantLock中可重入锁实现
实现原理:在AQS中维护了一个private volatile int state来计数重入次数,避免了频繁的持有释放操作,这样既提升了效率,又避免了死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 ) throw new Error ("Maximum lock count exceeded" ); setState(nextc); return true ; } return false ; }
公平锁/非公平锁 公平锁就是保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁(通过维护一个FIFO队列实现),而非公平锁则无法提供这个保障,ReentrantLock、ReadWriteLock默认都是非公平模式,因为非公平锁减少了线程挂起的几率,后来的线程有一定几率逃离被挂起的开销。 结合代码来看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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 ) throw new Error ("Maximum lock count exceeded" ); setState(nextc); return true ; } return false ; } 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
读写锁是怎样实现分别记录读写状态的 同步状态变量 state 的高16位用来表示读锁被获取的次数,低16位用来表示写锁的获取次数
1 2 3 4 5 6 static int sharedCount (int c) { return c >>> SHARED_SHIFT; }static int exclusiveCount (int c) { return c & EXCLUSIVE_MASK; }
写锁是怎样获取和释放的 写锁获取 写锁是独占式锁,在同一时刻写锁是不能被多个线程所获取,实现写锁的同步语义是通过重写AQS中的tryAcquire方法实现的:
其主要逻辑为:当读锁已经被读线程获取或者写锁已经被其他写线程获取,则写锁获取失败;否则,获取成功并支持重入,增加写状态
写锁释放 写锁释放通过重写AQS的tryRelease方法
1 2 3 4 5 6 7 8 9 10 11 12 13 protected final boolean tryRelease(int releases) { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); //1. 同步状态减去写状态 int nextc = getState() - releases; //2. 当前写状态是否为0,为0则释放写锁 boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null); //3. 不为0则更新同步状态 setState(nextc); return free; }
读锁是怎样获取和释放的 读锁的获取 当写锁被其他线程获取后,读锁获取失败,否则获取成功利用CAS更新同步状态。另外,当前同步状态需要加上SHARED_UNIT, 原因是同步状态的高16位用来表示读锁被获取的次数。如果CAS失败或者已经获取读锁的线程再次获取读锁时,是靠fullTryAcquireShared方法实现的
读锁的释放 读锁释放的实现主要通过方法tryReleaseShared,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 protected final boolean tryReleaseShared (int unused) { Thread current = Thread.currentThread(); if (firstReader == current) { if (firstReaderHoldCount == 1 ) firstReader = null ; else firstReaderHoldCount--; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); int count = rh.count; if (count <= 1 ) { readHolds.remove(); if (count <= 0 ) throw unmatchedUnlockException(); } --rh.count; } for (;;) { int c = getState(); int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) return nextc == 0 ; } }
乐观锁/悲观锁 参考链接: 面试必备之乐观锁与悲观锁
乐观锁 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现乐观锁适用于多读的应用类型,这样可以提高吞吐量
乐观锁的缺点(CAS算法的缺陷)
ABA问题(JDK 1.5 以后的 AtomicStampedReference 类解决)
循环时间长开销大: 自旋CAS,也就是不成功就一直循环执行直到成功,如果长时间不成功,会给CPU带来非常大的执行开销
只能保证一个共享变量的原子操作: CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效, 从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作
悲观锁 每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现