线程同步ReentrantLock,condition(await,signal)
来源:互联网 发布:开心农场php源码 编辑:程序博客网 时间:2024/05/18 16:18
一.synchronized和ReentrantLock
故事
1. 我们前面(二)java 的线程同步(synchronized ,wait,notify)讲了synchronized的同步方法,java 就是这么神奇,这里又有一个同步的方法!当然,长江后浪推前浪,后者肯定比前者是由优越之处的。
先看看 synchronized 的一些限制:
1.1 :无法中断正在等候获取一个锁的线程
1.2 :无法通过投票得到一个锁
1.3 :释放锁的操作只能与获得锁所在的代码块中进行,无法在别的代码块中释放锁 。
ReentrantLock
类实现了 Lock
,它拥有与 synchronized
相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。
定时锁等候:设置定时等候之后,在这个等候时间内如果没有获得这个锁,这个线程就会自己中断。
可中断锁等候:就是线程等候可以自己中断也可以别人中断。
锁投票:这个不太懂,有懂的大牛给提示一下,我到时引用到博文里面来(会注明作者的)。
二.ReentrantLock
的简单用法:
1.lock() 和unlock():ReentrantLock手动获取和释放锁,一般放在try catch 块中。如下所示
- class X {
- private final ReentrantLock lock = new ReentrantLock();
- // ...
- public void m() {
- lock.lock(); // block until condition holds
- try {
- // ... 同步的代码
- } finally {
- lock.unlock()
- }
- }
- }
2.既然有锁,那多个线程下面的阻塞注明做到呢?这就需要用到Condiction类了,这个类的作用就相当于给ReentrantLock加类似synchronized的wait,notify方法,不过它更强大!它的await可以有条件的等待,比如定时等待等用法,为方便比较,我结合前面synchronized的例子改一下,例子如下所示:
- package com.hxw.Threads;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.ReentrantLock;
- public class ProducerConsumeGameLock {
- /**
- * @param args
- */
- public static void main(String[] args) {
- Bucket bucket = new Bucket(); // 构造篮子
- new Thread(new Producer(bucket),"Producer线程").start();
- new Thread(new Consumer(bucket),"Consumer线程").start();
- }
- }
- final class Consumer implements Runnable {
- private Bucket bucket;
- public Consumer(Bucket bucket) {
- this.bucket = bucket;
- }
- @Override
- public void run() {
- for (int i = 0; i < 10; i++) {
- bucket.get();
- }
- }
- }
- final class Producer implements Runnable {
- private Bucket bucket;
- public Producer(Bucket bucket) {
- this.bucket = bucket;
- }
- @Override
- public void run() {
- for (int i = 0; i < 10; i++) { // 来回十次交易
- bucket.put((int) (Math.random() * 1000));
- }
- }
- }
- class Bucket {
- private volatile int packOdBalls;
- private volatile boolean available = false;
- private final ReentrantLock lock = new ReentrantLock();
- private Condition noBull = lock.newCondition();
- private Condition fullBull = lock.newCondition();
- public int get() { // 消费者从篮子里面取出球
- lock.lock();
- try {
- while (available == false) { // 如果没有就等着,但是为什么不是if呢?后面会做解析
- System.out.println("消费者:暂时没有球可以消费我就等着...");
- noBull.await(); // wait();
- }
- System.out.println("消费者获得了" + packOdBalls + "个球");
- available = false;
- System.out.println("持有消费线程数: "+lock.getHoldCount());
- fullBull.signal(); // notify();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- // 有的话就取出来
- return packOdBalls;
- }
- public synchronized void put(int packOdBalls) { // 生产者将生产球并放入到篮子里面
- lock.lock();
- try {
- while (available) {
- System.out.println("生产者:既然篮子里面已经有球了我就消费完了再生产吧!");
- fullBull.await(); // wait();
- }
- this.packOdBalls = packOdBalls;
- available = true;
- System.out.println("生产者放进去了" + packOdBalls + "个球");
- noBull.signal(); // notify();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- }
- }
【运行结果与前面一样,需要看的可以戳文章头的链接】
三.ReentrantLock的方法…
1. public boolean tryLock():
仅在调用时锁未被另一个线程保持的情况下,才获取该锁。 这个方法会完全忽略公平策略,理解trylock的意思就知道了,就是“努力试着去锁”,所以一旦这个锁没有线程持有,他就抢占了!
2. public boolean tryLock(long timeout,TimeUnit unit):
如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。说的详细点如果超出了指定的等待时间,则返回值为 false
。如果该锁没有被另一个线程保持,并且立即返回 true 值,则将锁的保持计数设置为 1。如果为了使用公平的排序策略,已经设置此锁,并且其他线程都在等待该锁,则不会 获取一个可用的锁。这与 tryLock() 方法相反。如果想使用一个允许闯入公平锁的定时 tryLock,那么可以将定时形式和不定时形式组合在一起:
if (lock.tryLock() || lock.tryLock(timeout, unit) ) { ... }
注意:有的人可能不理解这两个参数,其实就是前面是数词,后面是量词(SECOND,HOUR等等)
3. public int getHoldCount()查询当前线程保持此锁的次数。
4. public boolean isLocked()查询此锁是否由任意线程保持。
等等还有很多方法,这些查询方法都只能叫监控方法,因为线程无时无刻不在变化,可能返回这个值的下一纳秒,这个值就变了
5.还有一个构造方法:
public ReentrantLock(boolean fair)
创建一个具有给定公平策略的 ReentrantLock。
四.Condition方法一览…
1.await():
让当前线程一直处于等待状态,直到唤醒或中断。需要注意的是,这里说的是当前线程,这个“当前”指的是这个await所属Condition对象的线程创建者,这一点从上面的源码也可以看出(我们每个线程都new了一个Condition)。
对于唤醒,这里有个虚假唤醒,虚假唤醒也会引起当前线程唤醒,我们后面来讨论
2. boolean await(long time,TimeUnit unit):
让当前线程在指定时间内一直处于等待状态,直到被唤醒或中断。
3. void signal():唤醒一个等待线程。这个与notify功能类似
4. void signalAll():唤醒所有等待线程。这个与notifyAll功能类似
五.虚假唤醒
虚假唤醒简而言之就是一个signal()可能唤醒了多个线程,前面代码第60行左右提到过。当然这个概率是很低的,你会发现,你把while改成if也是可以运行的。但是我们也要避免这种情况,所以我们就改成了while,while()不仅仅在等待条件变量前检查条件变量,实际上在等待条件变量后也检查条件变量。
这样对condition进行多做一次判断,即可避免“虚假唤醒”.
一、Condition 类
在前面我们学习与synchronized锁配合的线程等待(Object.wait)与线程通知(Object.notify),那么对于JDK1.5 的 java.util.concurrent.locks.ReentrantLock 锁,JDK也为我们提供了与此功能相应的类java.util.concurrent.locks.Condition。Condition与重入锁是通过lock.newCondition()方法产生一个与当前重入锁绑定的Condtion实例,我们通知该实例来控制线程的等待与通知。该接口的所有方法:
public interface Condition { //使当前线程加入 await() 等待队列中,并释放当锁,当其他线程调用signal()会重新请求锁。与Object.wait()类似。 void await() throws InterruptedException; //调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。 //调用该方法后,结束等待的唯一方法是其它线程调用该条件对象的signal()或signalALL()方法。等待过程中如果当前线程被中断,该方法仍然会继续等待,同时保留该线程的中断状态。 void awaitUninterruptibly(); // 调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。 //nanosTimeout指定该方法等待信号的的最大时间(单位为纳秒)。若指定时间内收到signal()或signalALL()则返回nanosTimeout减去已经等待的时间; //若指定时间内有其它线程中断该线程,则抛出InterruptedException并清除当前线程的打断状态;若指定时间内未收到通知,则返回0或负数。 long awaitNanos(long nanosTimeout) throws InterruptedException; //与await()基本一致,唯一不同点在于,指定时间之内没有收到signal()或signalALL()信号或者线程中断时该方法会返回false;其它情况返回true。 boolean await(long time, TimeUnit unit) throws InterruptedException; //适用条件与行为与awaitNanos(long nanosTimeout)完全一样,唯一不同点在于它不是等待指定时间,而是等待由参数指定的某一时刻。 boolean awaitUntil(Date deadline) throws InterruptedException; //唤醒一个在 await()等待队列中的线程。与Object.notify()相似 void signal(); //唤醒 await()等待队列中所有的线程。与object.notifyAll()相似 void signalAll();}
二、使用
1、await() 等待 与 singnal()通知
1 package com.jalja.org.base.Thread; 2 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.locks.Condition; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 /** 8 * Condition 配合Lock 实现线程的等待 与通知 9 */10 public class ConditionTest{11 public static ReentrantLock lock=new ReentrantLock();12 public static Condition condition =lock.newCondition();13 public static void main(String[] args) {14 new Thread(){15 @Override16 public void run() {17 lock.lock();//请求锁18 try{19 System.out.println(Thread.currentThread().getName()+"==》进入等待");20 condition.await();//设置当前线程进入等待21 }catch (InterruptedException e) {22 e.printStackTrace();23 }finally{24 lock.unlock();//释放锁25 }26 System.out.println(Thread.currentThread().getName()+"==》继续执行");27 } 28 }.start();29 new Thread(){30 @Override31 public void run() {32 lock.lock();//请求锁33 try{34 System.out.println(Thread.currentThread().getName()+"=》进入");35 Thread.sleep(2000);//休息2秒36 condition.signal();//随机唤醒等待队列中的一个线程37 System.out.println(Thread.currentThread().getName()+"休息结束");38 }catch (InterruptedException e) {39 e.printStackTrace();40 }finally{41 lock.unlock();//释放锁42 }43 } 44 }.start();45 }46 }
执行结果:
Thread-0==》进入等待Thread-1=》进入Thread-1休息结束Thread-0==》继续执行
流程:在调用await()方法前线程必须获得重入锁(第17行代码),调用await()方法后线程会释放当前占用的锁。同理在调用signal()方法时当前线程也必须获得相应重入锁(代码32行),调用signal()方法后系统会从condition.await()等待队列中唤醒一个线程。当线程被唤醒后,它就会尝试重新获得与之绑定的重入锁,一旦获取成功将继续执行。所以调用signal()方法后一定要释放当前占用的锁(代码41行),这样被唤醒的线程才能有获得锁的机会,才能继续执行。
三、JDK中对Condition 的使用
我们来看看java.util.concurrent.ArrayBlockingQueue;
基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
看看他的put方法:
public void put(E e) throws InterruptedException { checkNotNull(e);//对传入元素的null判断 final ReentrantLock lock = this.lock; lock.lockInterruptibly();//对put()方法做同步 try { while (count == items.length)//如果队列已满 notFull.await();//让当前添加元素的线程进入等待状态 insert(e);// 如果有其他线程调用signal() 通知该线程 ,则进行添加行为 } finally { lock.unlock();//释放锁 } } private E extract() { final Object[] items = this.items; E x = this.<E>cast(items[takeIndex]); items[takeIndex] = null; takeIndex = inc(takeIndex); --count; notFull.signal();//唤醒一个在Condition等待队列中的线程 return x; }
- 线程同步ReentrantLock,condition(await,signal)
- join(),ReentrantLock结合Condition的await(),signal()的使用
- 黑马程序员——Java多线程—线程同步—Condition:await、signal、signalAll
- java 线程 Lock 锁使用Condition实现线程的等待(await)与通知(signal)
- 【Java基础之线程同步(三)】使用ReentrantLock Condition实现线程同步
- Condition的await-signal流程详解
- 【转】Condition的await-signal流程详解
- Condition的await-signal流程详解
- condition await signal 和 notity wait
- Condition的await-signal流程详解(转载)
- Java多线程(3):使用Condition中的await、signal进行线程间协作
- java线程之Condition、ReentrantLock
- wait()、notify()和notifyAll()、sleep()、Condition、await()、signal()
- Python 线程调度(Condition),线程同步
- java 线程同步的那些事: yield(), sleep(), wait(), await(), signal(), sginalAll()
- java 线程同步的那些事: yield(), sleep(), wait(), await(), signal(), sginalAll()
- 线程之间的协作await/signal
- 线程锁ReentrantLock和Condition的使用
- hive+hbase
- 算法学习笔记15-哈希算法
- 阿里云的ECS机器部署Nodejs项目
- jdk工具--jps
- oracle-using/natural join
- 线程同步ReentrantLock,condition(await,signal)
- 安卓使用万能适配器时候不能再item布局最外层布局进行tag设置
- 大一下期末考试:进制转化
- Maven详解
- Android 华为手机存储图片以及下载apk需要申请动态权限
- sc2017新高二&高一模拟赛8 总结
- jvisualvm远程连接tomcat调试
- habse启动zookper出现了错误,导致集群无法启动,谁知道怎么解决
- 关于SpringMVC异常处理