JDK并发工具之同步控制
一.synchronized的功能扩展:重入锁(java.util.concurrent.locks.ReentrantLock)
重入锁可以完全替代synchronized关键字。在JDK 5.0的早期版本中,重入锁的性能远远好于synchronized,但从JDK 6.0开始,JDK在syn-chronized上做了大量的优化,使得两者的性能差距并不大。
01 public class ReenterLock implements Runnable{
02 public static ReentrantLock lock=new ReentrantLock();
03 public static int i=0;
04 @Override
05 public void run() {
06 for(int j=0;j<10000000;j++){
07 lock.lock();
08 try{
09 i++;
10 }finally{
11 lock.unlock();
12 }
13 }
14 }
15 public static void main(String[] args) throws InterruptedException {
16 ReenterLock tl=new ReenterLock();
17 Thread t1=new Thread(tl);
18 Thread t2=new Thread(tl);
19 t1.start();t2.start();
20 t1.join();t2.join();
21 System.out.println(i);
22 }
23 }
与synchronized相比,重入锁有着显示的操作过程。开发人员必须手动指定何时加锁,何时释放锁。也正因为这样,重入锁对逻辑控制的灵活性要远远好于synchronized。但值得注意的是,在退出临界区时,必须记得释放锁。
重入锁是可以反复进入的,这里的反复仅仅局限于一个线程。上述代码的第7~12行,可以写成下面的形式:
lock.lock();
lock.lock();
try{
i++;
}
finally{
lock.unlock();
lock.unlock();
}
重入锁还提供了一些高级功能。比如,重入锁可以提供中断处理的能力。
1.中断响应
对于synchronized来说,如果一个线程在等待锁,那么结果只有两种情况,要么它获得这把锁继续执行,要么它就保持等待。而使用重入锁,则提供另外一种可能,那就是线程可以被中断。也就是在等待锁的过程中,程序可以根据需要取消对锁的请求。
try {
lock.lockInterruptibly(); 申请锁(替换上面的 lock.lock();)
}catch{}
finally
{ if (lock.isHeldByCurrentThread()) {
lock1.unlock();
}
}
可通过 t2.interrupt(); 中断 进行通知 并捕获释放锁
2.锁申请等待限时
除了等待外部通知之外,要避免死锁还有一种方法,那就是限时等待。
我们可以使用tryLock()方法进行一次限时的等待
if(lock.tryLock(5, TimeUnit.SECONDS)) {}
else {}
tryLock()方法接收两个参数,一个表示等待时长,另外一个表示计时单位。
ReentrantLock.tryLock()方法也可以不带参数直接运行。在这种情况下,当前线程会尝试获得锁,如果锁并未被其他线程占用,则申请锁会成功,并立即返回true。如果锁被其他线程占用,则当前线程不会进行等待,而是立即返回false。这种模式不会引起线程等待,因此也不会产生死锁
3.公平锁
如果我们使用synchronized关键字进行锁控制,那么产生的锁就是非公平的。
而重入锁允许我们对其公平性进行设置。它有一个如下的构造函数:
public ReentrantLock(boolean fair)
当参数fair为true时,表示锁是公平的。公平锁看起来很优美,但是要实现公平锁必然要求系统维护一个有序队列,因此公平锁的实现成本比较高,性能相对也非常低下,因此,默认情况下,锁是非公平的。如果没有特别的需求,也不需要使用公平锁。公平锁和非公平锁在线程调度表现上也是非常不一样的。
对于非公平锁,根据系统的调度,一个线程会倾向于再次获取已经持有的锁,这种分配方式是高效的,但是无公平性可言。
对上面ReentrantLock的几个重要方法整理如下。
•lock():获得锁,如果锁已经被占用,则等待。
•lockInterruptibly():获得锁,但优先响应中断。
•tryLock():尝试获得锁,如果成功,返回true,失败返回false。该方法不等待,立即返回。
•tryLock(long time, TimeUnit unit):在给定时间内尝试获得锁。
•unlock():释放锁。
就重入锁的实现来看,它主要集中在Java层面。在重入锁的实现中,主要包含三个要素:
第一,是原子状态。原子状态使用CAS操作(在第4章进行详细讨论)来存储当前锁的状态,判断锁是否已经被别的线程持有。
第二,是等待队列。所有没有请求到锁的线程,会进入等待队列进行等待。待有线程释放锁后,系统就能从等待队列中唤醒一个线程,继续工作。
第三,是阻塞原语park()和unpark(),用来挂起和恢复线程。没有得到锁的线程将会被挂起。有关park()和unpark()的详细介绍,可以参考3.1.7线程阻塞工具类:LockSupport。
重入锁的好搭档:Condition条件
它和wait()和notify()方法的作用是大致相同的。但是wait()和notify()方法是和synchro-nized关键字合作使用的,而Condtion是与重入锁相关联的。通过Lock接口(重入锁就实现了这一接口)的Condition newCondition()方法可以生成一个与当前重入锁绑定的Condition实例。利用Condition对象,我们就可以让线程在合适的时间等待,或者在某一个特定的时刻得到通知,继续执行。
Condition接口提供的基本方法如下:
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
•await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()或者sig-nalAll()方法时,线程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。
•awaitUninterruptibly()方法与await()方法基本相同,但是它并不会在等待过程中响应中断。
•singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obejct.notify()方法
在JDK内部,重入锁和Condition对象被广泛地使用,以ArrayBlockingQueue为例,它的put()方法实现如下:
二、允许多个线程同时访问:信号量(Semaphore)
信号量为多线程协作提供了更为强大的控制方法。广义上说,信号量是对锁的扩展。无论是内部锁synchronized还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源,而信号量却可以指定多个线程,同时访问某一个资源。
信号量主要提供了以下构造函数:
public Semaphore(int permits)
public Semaphore(int permits, boolean fair) //第二个参数可以指定是否公平
主要方法有:
public void acquire()
public void acquireUninterruptibly()
public boolean tryAcquire()
public boolean tryAcquire(long timeout, TimeUnit unit)
public void release()
三、ReadWriteLock读写锁
private static ReentrantReadWriteLock readWriteLock=newReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
•读-读不互斥:读读之间不阻塞。
•读-写互斥:读阻塞写,写也会阻塞读。
•写-写互斥:写写阻塞。如果在系统中,读操作次数远远大于写操作,则读写锁就可以发挥最大的功效,提升系统的性能。
四、倒计时器:CountDownLatch
对于倒计时器,一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。只有等所有的检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程等待所有检查线程全部完工后,再执行。
构造函数:
public CountDownLatch(int count)
CountDownLatch end = new CountDownLatch(10);//创建实例 计数数量为10。这表示需要有10个线程完成任务,等待在CountDownLatch上的线程才能继续执行。
end.countDown(); //通知Count-DownLatch,一个线程已经完成了任务,倒计时器可以减1啦。
end.await(); //等待 要求主线程等待所有10个检查任务全部完成。待10个任务全部完成后,主线程才能继续执行。
五、循环栅栏:CyclicBarrier
CyclicBarrier是另外一种多线程并发控制实用工具。和CountDownLatch非常类似,它也可以实现线程间的计数等待,但它的功能比CountDown-Latch更加复杂且强大。
前面Cyclic意为循环,也就是说这个计数器可以反复使用。比如,假设我们将计数器设置为10,那么凑齐第一批10个线程后,计数器就会归零,然后接着凑齐下一批10个线程,这就是循环栅栏内在的含义。
CyclicBarrier的使用场景也很丰富。比如,司令下达命令,要求10个士兵一起去完成一项任务。这时,就会要求10个士兵先集合报道,接着,一起雄赳赳气昂昂地去执行任务。当10个士兵把自己手头的任务都执行完成了,那么司令才能对外宣布,任务完成!
比CountDownLatch略微强大一些,CyclicBarrier可以接收一个参数作为barrierAc-tion。所谓barrierAction就是当计数器一次计数完成后,系统会执行的动作。如下构造函数,其中,parties表示计数总数,也就是参与的线程总数。
public CyclicBarrier(int parties, Runnable barrierAction)
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
cyclic.await();
CyclicBarrier.await()方法可能会抛出两个异常。
一个是InterruptedException,也就是在等待过程中,线程被中断,应该说这是一个非常通用的异常。大部分迫使线程等待的方法都可能会抛出这个异常,使得线程在等待时依然可以响应外部紧急事件。
另外一个异常则是CyclicBarrier特有的BrokenBarrierException。一旦遇到这个异常,则表示当前的CyclicBarrier已经破损了,可能系统已经没有办法等待所有线程到齐了。如果继续等待,可能就是徒劳无功的,因此,还是就地散货,打道回府吧。
六、线程阻塞工具类:LockSupport
LockSupport是一个非常方便实用的线程阻塞工具,它可以在线程内任意位置让线程阻塞。和Thread.suspend()相比,它弥补了由于resume()在前发生,导致线程无法继续执行的情况。和Ob-ject.wait()相比,它不需要先获得某个对象的锁,也不会抛出InterruptedException异常。LockSupport的静态方法park()可以阻塞当前线程,类似的还有parkNanos()、parkUntil()等方法。它们实现了一个限时的等待。
LockSupport.park(); //阻塞线程t1
LockSupport.unpark(t1); //释放阻塞
当然,我们依然无法保证unpark()方法发生在park()方法之后。但是执行这段代码,你会发现,它自始至终都可以正常的结束,不会因为park()方法而导致线程永久性的挂起。
这是因为LockSupport类使用类似信号量的机制。它为每一个线程准备了一个许可,如果许可可用,那么park()函数会立即返回,并且消费这个许可(也就是将许可变为不可用),如果许可不可用,就会阻塞。而unpark()则使得一个许可变为可用(但是和信号量不同的是,许可不能累加,你不可能拥有超过一个许可,它永远只有一个)。这个特点使得:即使unpark()操作发生在park()之前,它也可以使下一次的park()操作立即返回。这也就是上述代码可顺利结束的主要原因。
同时,处于park()挂起状态的线程不会像sus-pend()那样还给出一个令人费解的Runnable的状态。它会非常明确地给出一个WAITING状态,甚至还会标注是park()引起的
此外,如果你使用park(Object)函数,还可以为当前线程设置一个阻塞对象。这个阻塞对象会出现在线程Dump中。这样在分析问题时,就更加方便了。在堆栈信息中,我们可以看出当前线程等待的对象。
除了有定时阻塞的功能外,LockSup-port.park()还能支持中断影响。但是和其他接收中断的函数很不一样,LockSupport.park()不会抛出InterruptedException异常。它只是会默默的返回,但是我们可以从Thread.interrupted()等方法获得中断标记。
JDK并发工具之同步控制的更多相关文章
- JDK并发工具类
在JDK的并发包里提供了几个非常有用的并发工具类.CountDownLatch.CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了在线程 ...
- Java并发程序设计(四)JDK并发包之同步控制
JDK并发包之同步控制 一.重入锁 重入锁使用java.util.concurrent.locks.ReentrantLock来实现.示例代码如下: public class TryReentrant ...
- 并发工具类(三)控制并发线程的数量 Semphore
前言 JDK中为了处理线程之间的同步问题,除了提供锁机制之外,还提供了几个非常有用的并发工具类:CountDownLatch.CyclicBarrier.Semphore.Exchanger.Ph ...
- 并发工具类:CountDownLatch、CyclicBarrier、Semaphore
在多线程的场景下,有些并发流程需要人为来控制,在JDK的并发包里提供了几个并发工具类:CountDownLatch.CyclicBarrier.Semaphore. 一.CountDownLatch ...
- Java并发工具类 - CountDownLatch
Java并发工具类 - CountDownLatch 1.简介 CountDownLatch是Java1.5之后引入的Java并发工具类,放在java.util.concurrent包下面 http: ...
- java中的并发工具类
在jdk的并发包里提供了几个非常有用的并发工具类.CountDownLatdch.CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了在线 ...
- Java中的并发工具类(CountDownLatch、CyclicBarrier、Semaphore、Exchanger)
在JDK的并发包里提供了很多有意思的并发工具类.CountDownLatch.CyclicBarrier和Semaphore 工具类提供了一种并发流程控制的手段,Exchanger 工具类则提供了在线 ...
- Java并发编程-并发工具类及线程池
JUC中提供了几个比较常用的并发工具类,比如CountDownLatch.CyclicBarrier.Semaphore. CountDownLatch: countdownlatch是一个同步工具类 ...
- 并发工具类(一)等待多线程的CountDownLatch
前言 JDK中为了处理线程之间的同步问题,除了提供锁机制之外,还提供了几个非常有用的并发工具类:CountDownLatch.CyclicBarrier.Semphore.Exchanger.Ph ...
随机推荐
- 2016-2017-2 《Java程序设计》第5周学习总结
学号 2016-2017-2 <Java程序设计>第5周学习总结 教材部分学习内容总结 第八章: 一.语法与继承架构 使用try.catch: •执行流程 1.尝试执行try区块中程序代码 ...
- 微信公众号为什么要加粉?流量,广告,KPI,吸粉,增粉
微信公众号为什么要加粉?流量,广告,KPI,吸粉,增粉 1.曾有人这样比喻:当你的粉丝超过100人时,你就像是一本内刊:超过1000人,你就像个布告栏:超过1万人,你就好比一本杂志:超过10万人,你就 ...
- Vue源码解析之nextTick
Vue源码解析之nextTick 前言 nextTick是Vue的一个核心功能,在Vue内部实现中也经常用到nextTick.但是,很多新手不理解nextTick的原理,甚至不清楚nextTick的作 ...
- Django框架----视图函数补充
视图函数的补充 1.视图函数:一定是要包含两个对象的(render源码里面有HttpResponse对象) request对象:----->所有的请求信息 HttpResponse:-- ...
- WindowsServer-性能计数器
https://jingyan.baidu.com/article/59703552e764e48fc00740dd.html
- PHP发送HTTP请求的6种方法
方法1: 用 file_get_contents 以get方式获取内容: <?php$url = 'https://wenda.shukaiming.com/';echo file_get_co ...
- P1852 [国家集训队]跳跳棋
P1852 [国家集训队]跳跳棋 lca+二分 详细解析见题解 对于每组跳棋,我们可以用一个三元组(x,y,z)表示 我们发现,这个三元组的转移具有唯一性,收束性 也就是说,把每个三元组当成点,以转移 ...
- 20145122《 Java网络编程》实验五实验报告
实验名称 Java网络编程 实验内容 1.掌握Socket程序的编写: 2.掌握密码技术的使用: 3.设计安全传输系统. 结对小伙伴 20145120黄玄曦 博客地址:http://www.cnblo ...
- 20145331魏澍琛《网络对抗》Exp4 恶意代码分析
20145331魏澍琛<网络对抗>Exp4 恶意代码分析 基础问题回答 1.如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所有想监控下系统一天天的到底在干些什么.请设计下你想监控的操作 ...
- stl string 使用(转载)
出处:http://www.cnblogs.com/lzjsky/archive/2011/01/23/1942508.html 1. 查找字符 std::wstring strData = L&qu ...