一.Lock接口(java.util.concurrent.locks):
- void lock():获取锁,阻塞方式;如果资源已被其他线程锁定,那么lock将会阻塞直到获取锁,锁阻塞期间不受线程的Interrupt的影响,在获取锁成功后,才会检测线程的interrupt状态,如果interrupt=true,则抛出异常。
- unlock():释放锁
- tryLock():尝试获取锁,并发环境中"闯入"行为,如果有锁可用,直接获取锁并返回true,否则范围false.
- lockInterruptibly():尝试获取锁,并支持"中断"请求。与lock的区别时,此方法的开始、结束和执行过程中,都会不断检测线程的interrupt状态,如果线程被中断,则立即抛出异常;而不像lock方法那样只会在获取锁之后才检测。
二.Lock接口实现类
Lock直接实现,只有3个类:ReentrantLock和WriteLock/ReadLock;这三种锁;Lock和java的synchronized(内置锁)的功能一致,均为排他锁.
ReentrantLock为重入排他锁,对于同一线程,如果它已经持有了锁,那么将不会再次获取锁,而直接可以使用.
ReentrantReadWriteLock并没有继承ReentrantLock,而是一个基于Lock接口的单独实现.它实现了 ReadWriteLock,即读写分离锁,是一种采用锁分离技巧的API.
尽管在API级别ReentrantReadWriteLock和ReentrantLock没有直接继承关系,但是ReentrantReadWriteLock中的ReadLock和WriteLock都具有ReentrantLock的全部语义(简单说,就是把ReentrantLock的代码copy了一下.),即锁的可重入性.WriteLock支持Condition(条件),ReadLock不支持.
Lock的实现类中,都包含了2中锁等待策略:公平和非公平;其实他们的实现也非常简单,底层都是使用了queue来维持锁请求顺序.[参考:http://shift-alt-ctrl.iteye.com/blog/1839142]
公平锁,就是任何锁请求,首先将请求加入队列,然后再有队列机制来决定,是阻塞还是分配锁.
非公平,就是允许"闯入",当然公平锁,也无法干扰"闯入",对于任何锁请求,首先检测锁状态是否可用,如果可用直接获取,否则加入队列..
ReentrantLock本质上和synchronized修饰词是同一语义,如果一个线程lock()之后,其他线程进行lock时必须阻塞,直到当前线程的前续线程unlock.[执行lock操作时,将会被队列化(假如在公平模式下),获取lock的线程都将具有前续/后继线程,前续线程就是当前线程之前执行lock操作而阻塞的线程,后继线程就是当前线程之后执行lock操作的线程;那么对于unlock操作就是"解锁"信号的传递,如果当前线程unlock,那么将会触发后继线程被"唤醒",即它因为lock操作阻塞状态被解除.];这是ReentrantLock的基本原理,但是当ReentrantLock在Conditon情况下,事情就变得更加复杂.[参加下述]
三.Condition:锁条件
Condition与Lock形成happen-before关系。Condition将Object的监视器方法(wait,notify,notifyAll)分解成截然不同的对象,以便通过这些对象与任意Lock实现组合。使Lock具有等待“集合”的特性,或者“类型”;Lock替代了synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。(synchronized + object.wait对应Lock + Condition.await)
Condition又称条件队列,为线程提供了一个含义,以便在某种状态条件现在可能为true的其他线程通知它之前,一直挂起该线程。即多个线程,其中一个线程因为某个条件而阻塞,其他线程当“条件”满足时,则“通知”哪些阻塞的线程。这,几乎和object中wait和notify的机制一样。
Condition和wait一样,阻塞时也将原子性的释放锁(间接执行了release()方法)。并挂起线程。Condition必须与Lock形成关系,只有获取lock权限的,才能进行Condition操作。Condition底层基于AQS实现,条件阻塞,将以队列的方式,LockSupport支持。其实现类有ConditionObject,这也是Lock.newCondition()的返回实际类型,在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
- void await() throws InterruptedException:当前线程阻塞,并原子性释放对象锁。如下条件将触发线程唤醒:
- 当线程被中断(支持中断响应),
- 其他线程通过condition.signal()方法,且碰巧选中当前线程唤醒
- 其他线程通过condition.signalAll()方法
- 发生虚假唤醒
底层实现,await()方法将当前线程信息添加到Conditon内部维护的"await"线程队列的尾部(此队列的目的就是为singal方法保持亟待唤醒的线程的顺序),然后释放锁(执行tryRelease()方法,注意此处释放锁,仅仅是释放了锁信号,并不是unlock,此时其他线程仍不能获取锁--lock方法阻塞),然后使用LockSupport.park(this)来强制剥夺当前线程执行权限。await方法会校验线程的中断标记。
由此可见,await()方法执行之后,因为已经"归还"了锁信号,那么其他线程此时执行lock方法,将不再阻塞..
- void awaitUninterruptibly():阻塞,直到被唤醒。此方法不响应线程中断请求。即当线程被中断时,它将继续等待,直到接收到signal信号(你应该能想到"陷阱"),当最终从此方法返回时,仍然将设置其中断状态。
- void signal()/signalAll():唤醒一个/全部await的线程。
//////例子: private Lock lock = new ReentrantLock(); private Condition full = lock.newCondition(); private Condition empty = lock.newCondition(); public Object take(){ lock.lock(); try{ while(isEmpty()){ empty.await() } Object o = get() full.signalAll(); return o; }finally{ lock.unlock(); } } public void put(Object o){ lock.lock(); try{ while(isFull()){ full.await(); } put(o); empty.signalAll(); }finally{ lock.unlock(); } }
四.机制
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。注意,Lock 实例只是普通的对象,其本身可以在 synchronized 语句中作为目标使用。获取 Lock 实例的监视器锁与调用该实例的任何 lock() 方法没有特别的关系。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock 实例。
Lock接口具有的方法:
- void lock():获取锁,阻塞直到获取。
- void lockInterruptibly() throws InterrutedException:获取锁,阻塞直到获取成功,支持中断响应。
- boolean tryLock():尝试获取锁,返回是否获取的结果。如果碰巧获取成功,则返回true,此时已经持有锁。
- boolean tryLock(long time,TimeUnit) throws InterruptedException:尝试获取锁,获取成功返回true,超时时且没有获取锁则返回false。
- void unlock():释放锁。约定只有持有锁者才能释放锁,否则抛出异常。
- void newCondition():返回绑定到lock的条件。
五.ReadWriteLock
ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer(写锁),读取锁可以由多个 reader 线程同时保持(共享锁)。写入锁是独占的。所有 ReadWriteLock 实现都必须保证 writeLock 操作的内存同步效果也要保持与相关 readLock 的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。
- Lock readLock():返回读锁。
- Lock writeLock():返回写锁。
六.ReentrantLock
ReentrantLock,重入排它锁,它和synchronized具有相同的语义以及在监视器上具有相同的行为,但是功能更加强大。
ReetrantLock将由最近成功获得锁且还没有释放锁的线程标记为“锁占有者”;当锁没有被线程持有时,调用lock方法将会成功获取锁并返回,如果当前线程为锁持有者,再次调用lock将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。
ReentrantLock的构造方法,允许接收一个“公平策略”参数,“公平策略”下,多个线程竞争获取锁时,将会以队列化锁请求者,并将锁授予队列的head。在“非公平策略”下,则不完全保证锁获取的顺序,允许闯入行为(tryLock)。
ReentrantLock基于AQS机制,锁信号量为1,如果信号量为1且当前锁持有者不为自己,则不能获取锁。释放锁时,如果当前锁持有者不是自己,也将抛出“IllegalMonitorStateException”。由此可见,对于ReentrantLock,lock和release方法是需要组合出现。
七.ReentrantReadWriteLock:可重入读写分离锁
- 重入性 :当前线程可以重新获取相应的“读锁”或者“写锁”,在写入线程保持的所有写入锁都已经释放后,才允许重入reader(读取线程)使用它们。writer线程可以获取读锁,但是reader线程却不能直接获取写锁。
- 锁降级:重入还允许写入锁降级为读锁,其实现方式为:先获取写入锁,然后获取读取锁,最后释放写入锁。但是读取锁不能升级为写入锁。
- Conditon的支持:只有写入锁支持conditon,对于读取锁,newConditon方法直接抛出UnsupportedOperationException。
ReentrantReadWriteLock目前在java api中无直接使用。ReentrantReadWriteLock并没有继承自 ReentrantLock,而是单独重新实现。其内部仍然支持“公平性”“非公平性”策略。
ReentrantReadWriteLock基于AQS,但是AQS只有一个state来表示锁的状态,所以如果一个state表示2种类型的锁状态,它做了一个很简单的策略,“位运算”,将一个int类型的state拆分为2个16位段,左端表示readlock锁引用计数,右端16位表示write锁。在readLock、writeLock进行获取锁或者释放锁时,均是通过有效的位运算和位控制,来达到预期的效果。
八.ReadLock
- void lock():获取读取锁,伪代码如下:
//如果当前已经有“写锁”,且持有写锁者不是当前线程(如果是当前线程,则支持写锁,降级为读锁),则获取锁失败 //即任何读锁的获取,必须等待队列中的写锁释放 //c为实际锁引用量(exclusiveCount方法实现为:c & ((1<<16) -1) if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current) return -1; //CAS操作,操作state的左端16位。 if(CAS(c,c + (1<<16))){ return 1; }
- void unlock():释放read锁,即共享锁,伪代码如下:
//CAS锁引用 for (;;) { int c = getState(); int nextc = c - (1<<16);//位操作,释放一个锁。 if (compareAndSetState(c, nextc)) return nextc == 0; }
九.WriteLock
- void lock():获取写入锁,伪代码如下:
//当前线程 Thread current = Thread.currentThread(); //实际的锁引用state int c = getState(); //右端16位,通过位运算获取“写入锁”的state int w = exclusiveCount(c); //如果有锁引用 if (c != 0) { //且所引用不是自己 if (w == 0 || current != getExclusiveOwnerThread()){ return false; } } //如果写入锁state为0,且CAS成功,则设置state和独占线程信息 if ((w == 0 && writerShouldBlock(current)) ||!compareAndSetState(c, c + acquires)){ return false; } setExclusiveOwnerThread(current); return true;
- void unlock():释放写入锁,伪代码如下:
//计算释放锁的信号量 int nextc = getState() - releases; //对于写入锁,则校验当前线程是否为锁持有者,否则不可以释放(死锁) if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); //释放锁,且重置独占线程信息 if (exclusiveCount(nextc) == 0) { setExclusiveOwnerThread(null); setState(nextc); return true; } else { setState(nextc); return false; }
十.LockSupport:用来创建锁和其他同步类的基本线程阻塞原语。
底层基于hotspot的实现unsafe。park 和 unpark 方法提供了阻塞和解除阻塞线程的有效方法。三种形式的 park(即park,parkNanos(Object blocker,long nanos),parkUntil(Object blocker,long timestamp)) 还各自支持一个 blocker 对象参数。此对象在线程受阻塞时被记录,以允许监视工具和诊断工具确定线程受阻塞的原因。(这样的工具可以使用方法 getBlocker(java.lang.Thread) 访问 blocker。)建议最好使用这些形式,而不是不带此参数的原始形式。
在锁实现中提供的作为 blocker 的普通参数是 this。
- static void park(Object blocker):阻塞当前线程,直到如下情况发生:
- 其他线程,调用unpark方法,并将此线程作为目标而唤醒
- 其他线程中断当前线程此方法不报告,此线程是何种原因被放回,需要调用者重新检测,而且此方法也经常在while循环中执行
while(//condition,such as:queue.isEmpty){ LockSupport.park(queue);//此时queue对象作为“阻塞”点传入,以便其他监控工具查看,queue的状态 //检测当前线程是否已经中断。 if(Thread.interrupted()){ break; } }
- void getBlocker(Thread t):返回提供最近一次尚未解除阻塞的park的阻塞点。可以返回null。
- void unpark(Thread t):解除指定线程阻塞,使其可用。参数null则无效果。
LockSupport实例(不过不建议在实际代码中直接使用LockSupport,很多时候,你可以使用锁来控制):
/////////////Demo public class LockSupportTestMain { /** * @param args */ public static void main(String[] args) throws Exception{ System.out.println("Hear!"); BlockerObject blocker = new BlockerObject(); LThread tp = new LThread(blocker, false); LThread tt = new LThread(blocker, true); tp.start(); tt.start(); Thread.sleep(1000); } static class LThread extends Thread{ private BlockerObject blocker; boolean take; LThread(BlockerObject blocker,boolean take){ this.blocker = blocker; this.take = take; } @Override public void run(){ if(take){ while(true){ Object o = blocker.take(); if(o != null){ System.out.println(o.toString()); } } }else{ Object o = new Object(); System.out.println("put,,," + o.toString()); blocker.put(o); } } } static class BlockerObject{ Queue<Object> inner = new LinkedList<Object>(); Queue<Thread> twaiters = new LinkedList<Thread>(); Queue<Thread> pwaiters = new LinkedList<Thread>(); public void put(Object o){ inner.offer(o); pwaiters.offer(Thread.currentThread()); Thread t = twaiters.poll(); if(t != null){ LockSupport.unpark(t); } System.out.println("park"); LockSupport.park(Thread.currentThread()); System.out.println("park is over"); } public Object take(){ Thread t = pwaiters.poll(); if(t != null){ System.out.println("unpark"); LockSupport.unpark(t); System.out.println("unpark is OK"); } //twaiters.offer(Thread.currentThread()); return inner.poll(); } } }
备注:有时候会疑惑wait()/notify() 和Unsafe.park()/unpark()有什么区别?区别是wait和notify是Object类的方法,它们首选需要获得“对象锁”,并在synchronized同步快中执行。park和unpark怎不需要这么做。wait和park都是有当前线程发起,notify和unpark都是其他线程发起。wait针对的是对象锁,park针对的线程本身,但是最终的效果都是导致当前线程阻塞。Unsafe不建议开发者直接使用。
相关推荐
阿里专家级并发编程架构师级课程,完成课程的学习可以帮助同学们解决非常多的JAVA并发编程疑难杂症,极大的提高JAVA并发编程的效率。课程内容包括了JAVA手写线程池,UC线程池API详解,线程安全根因详解,锁与原子类...
java.util.concurrent包提供了创建并发应用程序的工具,本资源主要是对该api进行详细的解读,并对api的使用做出安全高效的引用建议.
接着,深入讲解了Java并发编程的核心API,如synchronized关键字、Lock接口、Condition接口、Semaphore等,帮助读者掌握Java并发编程的基本工具和方法。 除了基础知识和API的讲解,本书还重点介绍了Java并发编程的...
主要介绍了Java并发框架:Executor API详解,随着当今处理器中可用的核心数量的增加, 随着对实现更高吞吐量的需求的不断增长,多线程 API 变得非常流行。 Java 提供了自己的多线程框架,称为 Executor 框架,需要的...
课程内容包括了JAVA手写线程池,UC线程池API详解,线程安全根因详解,锁与原子类,分布式锁原理与实现方式,并发编程-AQS等等针对性非常强的JAVA编程开发教程,这其中的内容对JAVA开发技能的拔尖,非常的有帮助。...
有关java高并发知识总结:三种线程创建方式 深入理解Thread构造函数 Thread API #### CAS缺陷 ##### 循环时间长开销大,自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。 ##### 只能保证一个共享变量...
20.5 Java应用通过Hibernate API声明JDBC事务 20.5.1 处理异常 20.5.2 Session与事务的关系 20.5.3 设定事务超时 20.6 Java应用通过Hibernate API声明JTA事务 20.7 Java应用通过JTA API声明JTA事务 ...
锁锁机制,API详解 Fork/Join 框架机制详解 Executor线程池框架简介 面向对象 泛型机制与反射原理 Proxy动态代理机制详解 从整体上观察对象 网络开发 Servlet基础,生命周期执行过程 Http请求详解,握手挥手流程...
(注意,本资源附带书中源代码可供参考) 多线程与并发处理是程序设计好坏优劣的重要课题,本书通过浅显易懂的文字与实例来介绍Java线程相关的设计模式概念,并且通过实际的Java程序范例和 UML图示来一一解说,书中...
高并发编程第三阶段02讲 AtomicInteger API详解,以及CAS算法详细介绍.mkv 高并发编程第三阶段03讲 利用CAS构造一个TryLock自定义显式锁.mp4 高并发编程第三阶段04讲 利用CAS构造一个TryLock自定义显式锁-增强...
第三部分详细、深入地介绍volatile关键字的语义,volatile关键字在Java中非常重要,可以说它奠定了Java核心并发包的高效运行,在这一部分中,我们通过实例展示了如何使用volatile关键字以及非常详细地介绍了Java内存...
15.1.2 Java反射API412 15.1.3 Class类413 15.2 使用Java反射机制414 15.2.1 获取类型信息414 15.2.2 创建对象417 15.2.3 调用方法419 15.2.4 访问成员变量的值421 15.2.5 操作数组422 15.3 反射与动态代理424 ...
20.5 Java应用通过Hibernate API声明JDBC事务 20.5.1 处理异常 20.5.2 Session与事务的关系 20.5.3 设定事务超时 20.6 Java应用通过Hibernate API声明JTA事务 20.7 Java应用通过JTA API声明JTA事务 ...
20.5 Java应用通过Hibernate API声明JDBC事务 20.5.1 处理异常 20.5.2 Session与事务的关系 20.5.3 设定事务超时 20.6 Java应用通过Hibernate API声明JTA事务 20.7 Java应用通过JTA API声明JTA事务 ...
20.5 Java应用通过Hibernate API声明JDBC事务 20.5.1 处理异常 20.5.2 Session与事务的关系 20.5.3 设定事务超时 20.6 Java应用通过Hibernate API声明JTA事务 20.7 Java应用通过JTA API声明JTA事务 ...
高并发编程第三阶段02讲 AtomicInteger API详解,以及CAS算法详细介绍.mkv 高并发编程第三阶段03讲 利用CAS构造一个TryLock自定义显式锁.mp4 高并发编程第三阶段04讲 利用CAS构造一个TryLock自定义显式锁-增强...
MongoDB可视化客户端及JavaApi实践 手写基于MongoDB的ORM框架 MongoDB企业级集解决方案 MongoDB聚合、索引及基本执行命令 MongoDB数据分片、转存及恢复策略 MyCat MySQL主从复制及读写分离实战 MySQL+...
适合刚起步的学子