动易网站频道栏目字体大小修改,百度推广入口页面,wordpress安装用户名密码,wordpress页头概述
Java中可以通过加锁#xff0c;来保证多个线程访问某一个公共资源时#xff0c;资源的访问安全性。Java提出了两种方式来加锁
第一种是我们上文提到的通过关键字synchronized加锁#xff0c;synchronized底层托管给JVM执行的#xff0c;并且在java 1.6 以后做了很多…概述
Java中可以通过加锁来保证多个线程访问某一个公共资源时资源的访问安全性。Java提出了两种方式来加锁
第一种是我们上文提到的通过关键字synchronized加锁synchronized底层托管给JVM执行的并且在java 1.6 以后做了很多优化(偏向锁、自旋、轻量级锁)使用很方便且性能也很好,所以在非必要的情况下建议使用synchronized做同步操作第二种是本文将要介绍的通过java.util.concurrent包下的Lock来加锁(lock大量使用CAS自旋。因此根据CAS特性建议在低锁冲突的情况下使用lock) AQS 概述
AQS全称AbstractQueuedSynchronizer,译为抽象队列同步器AQS底层数据结构是被volatile修饰state和一个Node双向队列Lock下的实现类包括ReentrantLock、ReadLock、WriteLock底层都是基于AQS实现锁资源获取或释放
内部结构 根据源码我们可以知道AQS维护了一个volatile的state和一个CLHFIFO双向队列 state是一个由volatile修饰的int型互斥变量state0表示没有任务线程使用该资源而state1表示已经有线程正在持有锁资源。CLH队列是由内部类Node来维护的FIFO队列
实现原理
当一个线程获取锁资源时首先会判断state是否等于0(无锁状态),如果是0则把这个state更新为1,此时该锁资源被占用。在这个过程中,如果多个线程同时进行state更新操作,就会导致线程的安全性问题。因此AQS底层采用了CAS机制,来保证互斥变量state更新的原子性。未获得锁的线程通过Unsafe类中的park方法去进行阻塞把阻塞的线程按照先进先出的原则放到CLH双向链表中当获得锁的线程释放锁后会从这个双向链表的头部去唤醒下一个等待的线程再去竞争锁。
公平锁和非公平锁
在竞争锁资源时公平锁要判断双向链表中是否有阻塞的线程如果有则需要去排队等待。而非公平锁的处理方式是不管双向链表中是否有阻塞的线程在排队等待它都会去尝试修改state变量去竞争锁这对链表中排队的线程来说是非公平的。 Lock接口
Lock实现类 JDK8中,除了StampedLock为不可重入锁,其他包括ReentrantLock、ReentrantReadWriteLock以及Synchronized关键字都是可重入锁可重入锁是指一个线程抢占到了互斥锁资源且在锁释放之前可以重复获取该锁资源,只需要记录重入次数state递增1即可lock实际上是通过更新AQS中的state来控制锁的持有情况
Lock方法
// 尝试获取锁获取成功则返回否则阻塞当前线程
void lock();
// 尝试获取锁线程在成功获取锁之前被中断则放弃获取锁抛出异常
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁获取锁成功则返回true否则返回false
boolean tryLock();
// 尝试获取锁若在规定时间内获取到锁则返回true否则返回false未获取锁之前被中断则抛出异常
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 返回当前锁的条件变量通过条件变量可以实现类似notify和wait的功能一个锁可以有多个条件变量
Condition newCondition();ReentrantLock 根据源码可以看到实现锁功能的关键成员变量Sync类型的sync继承AQSSync在ReentrantLock中有两个实现类NonfairSync公平锁类型和FairSync非公平锁类型ReentrantLock默认是非公平锁实现,在实例化时可以指定选择公平锁或者非公平锁
ReentrantLock获取锁流程
//.lock()调用的是AQS的acquire()
public void lock() {sync.acquire(1);
}public final void acquire(int arg) {//tryAcquire会尝试通过CAS获取一次锁。//addWaiter将当前线程加入双向链表等待队列中//acquireQueued通过自旋判断当前队列节点是否可以获取锁if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}//---------------------非公平锁尝试获取锁的过程---------------------
protected final boolean tryAcquire(int acquires) {// AQS的nonfairTryAcquire()方法return nonfairTryAcquire(acquires);
}final boolean nonfairTryAcquire(int acquires) {// 获取当前线程final Thread current Thread.currentThread();// 获取stateint c getState();if (c 0) {// 目前没有线程获取锁通过CAS乐观锁去修改state的值if (compareAndSetState(0, acquires)) {// 设置持有锁的线程为当前线程setExclusiveOwnerThread(current);return true;}}// 锁的持有者是当前线程重入锁else if (current getExclusiveOwnerThread()) {// state 1int nextc c acquires;if (nextc 0) // overflowthrow new Error(Maximum lock count exceeded);setState(nextc);return true;}return false;
}//---------------------当前线程加入双向链表的过程---------------------
private Node addWaiter(Node mode) {Node node new Node(mode);for (;;) {// 获取末位节点Node oldTail tail;if (oldTail ! null) {// 当前节点的prev设置为原末位节点node.setPrevRelaxed(oldTail);// CAS确保在线程安全的情况下将当前线程加入到链表的尾部if (compareAndSetTail(oldTail, node)) {// 原末位节点的next设置为当前节点oldTail.next node;return node;}} else {// 链表为空则初始化initializeSyncQueue();}}
}//---------------------首节点自旋过程---------------------
final boolean acquireQueued(final Node node, int arg) {boolean interrupted false;try {for (;;) {final Node p node.predecessor();// 首节点线程去尝试竞争锁if (p head tryAcquire(arg)) {// 成功获取到锁从首节点移出FIFOsetHead(node);p.next null; // help GCreturn interrupted;}if (shouldParkAfterFailedAcquire(p, node))interrupted | parkAndCheckInterrupt();}} catch (Throwable t) {cancelAcquire(node);if (interrupted)selfInterrupt();throw t;}
}ReentrantLock释放锁流程
释放锁本质就是对AQS中的状态值State进行逐步递减操作
//.unlock()调用AQS的release()方法释放锁资源
public void unlock() {sync.release(1);
}public final boolean release(int arg) {// Sync的tryRelease()方法if (tryRelease(arg)) {Node h head;if (h ! null h.waitStatus ! 0)unparkSuccessor(h);return true;}return false;
}protected final boolean tryRelease(int releases) {// 获取状态int c getState() - releases;if (Thread.currentThread() ! getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free false;// 修改锁的持有者为nullif (c 0) {free true;setExclusiveOwnerThread(null);}setState(c);return free;
}ReentrantReadWriteLock ReentrantReadWriteLock读写锁可以分别获取读锁或写锁,即将数据的读写操作分开;writeLock()获取写锁 writeLock().lock():为写锁上锁writeLock().unlock():为写锁释放锁readLock()获取读锁 readLock().lock():为读锁上锁readLock().unlock():为读锁释放锁读锁使用共享模式,写锁使用独占模式。即不存在写锁时,读锁可以被多个线程同时持有;存在写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁;而当有读锁时,写锁就不能获得适用于读多写少应用场景,如缓存
Condition 概述
Condition也是一种线程通信的机制,通过await和singalAll()实现线程阻塞和唤醒底层数据结构是复用AQS的Node类,由不带头结点的链表实现的队列await实现原理通过LockSupport.park将当前线程置于Waiting阻塞状态,直到其他线程调用signal或signalAll将等待队列的队头结点移入到同步队列中,使其有机会通过自旋获取到锁signal/signalAll:将等待队列的队头结点移入到同步队列中,并通过LockSupport.unpark唤醒该线程与Object的wait/notify机制对比 Condition支持不响应中断,而object不能Lock可以支持多个condition等待队列,object只能支持一个Condition能够对await设置超时时间,而object不能可以通过LockCondition实现生产者-消费者问题(在后文并发实践篇会有相关示例)
Condition实践
package com.bierce;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*** Lock Condition: 实现线程按序打印* 案例开启3个线程,id分别为A、B、C,并打印10次,而且按顺序交替打印如ABCABCABC...*/
public class TestCondition {public static void main(String[] args) {PrintByOrderDemo print new PrintByOrderDemo();new Thread(() - {for (int i 1; i 10; i) {// 打印10次print.loopA(i);}},A).start();new Thread(() - {for (int i 1; i 10; i) {print.loopB(i);}},B).start();new Thread(() - {for (int i 1; i 10; i) {print.loopC(i);}},C).start();}
}
class PrintByOrderDemo{private int number 1; //当前正在执行线程的标记private Lock lock new ReentrantLock();private Condition ConditionA lock.newCondition();private Condition ConditionB lock.newCondition();private Condition ConditionC lock.newCondition();public void loopA(int totalLoop){lock.lock();try {if ( number ! 1){ //判断当前是否打印AConditionA.await();}for (int i 1; i 1; i) {System.out.print(Thread.currentThread().getName()); //打印A}//唤醒其他线程number 2;ConditionB.signal();}catch (Exception e){e.printStackTrace();} finally {lock.unlock();}}public void loopB(int totalLoop){lock.lock();try {if ( number ! 2){ //判断当前是否打印BConditionB.await();}for (int i 1; i 1; i) {System.out.print(Thread.currentThread().getName()); //打印B}//唤醒其他线程number 3;ConditionC.signal();}catch (Exception e){e.printStackTrace();} finally {lock.unlock();}}public void loopC(int totalLoop){lock.lock();try {if ( number ! 3){ //判断当前是否打印CConditionC.await();}for (int i 1; i 1; i) {System.out.print(Thread.currentThread().getName()); //打印C}//唤醒其他线程number 1;ConditionA.signal();}catch (Exception e){e.printStackTrace();} finally {lock.unlock();}}
}