Java多线程的同步控制记录
Java多线程的同步控制记录
一、重入锁
- 重入锁完全可以代替 synchronized 关键字。在JDK 1.5 早期版本,重入锁的性能优于 synchronized。JDK 1.6 开始,对于 synchronized 做了大量优化,使得两者性能差距不大。
代码示例:
- private static ReentrantLock lock = new ReentrantLock();
- @Override
- public void run() {
- lock.lock(); // 加锁
- try {
- // doSomething
- } finally {
- lock.unlock(); // 释放锁
- }
- }
PS:因为重入锁需要我们手动加锁/释放锁,比较好实现对于逻辑的控制。
注意事项:
- 加锁后记得释放锁,否则其他线程就没有机会访问(比如,临界区资源)。
- 一个线程多次获得锁(其实是同一把锁)。但加了多少次锁,就要释放多少次锁。同时,lock()方法会一直堵塞线程直到释放锁。
- 中断响应 : lockInterruptibly()方法是一个可以对中断进行响应的锁申请动作(在等待锁的过程中,可以响应中断)。
- 锁申请等待限时 : tryLock()方法给定一个等待时间,让线程自动放弃等待,避免死锁(PS:当前线程会尝试获得锁,如果锁未被其他线程占用,则申请成功返回true;否则返回false)。
- 公平锁:
- 通过ReentrantLock(true)的构造函数来启用公平锁。
- 按申请时间顺序来获得锁,不会产生饥饿现象。
- 系统需要维护一个有序队列,实现成本较高且性能相对低下(一般情况下使用非公平锁)。
ReentrantLock的几个重要方法
- lock():获得锁,如果锁已经被占用,则等待。
- lockInterruptibly():获得锁,但优先响应中断。
- tryLock():立刻/给定时间内尝试获得锁(可以开启公平锁)。
- unlock():释放锁。
二、Condition条件
Condition 与重入锁相关联。利用 Condition 可以让线程在合适的时间内等待,或者在某一个特定的时刻得到通知,继续执行。
调用代码如下:
- private static ReentrantLock lock = new ReentrantLock();
- private static Condition condition = lock.newCondition();
常用的方法:
- await() 当前线程等待,同时释放锁。其他线程可以使用 signal() 或 signalAll() 方法使线程获得锁并继续执行。或者线程中断时,也能跳出等待。
- awaitUninterruptibly() 方法与 await() 方法基本相同,,但等待过程中不会响应中断。
- signal() 方法唤醒一个在等待中的线程;signalAll() 方法唤醒所有在等待中的线程。
用一个实例来理解 Condition 对象
代码如下:
- public class MyThread implements Runnable {
- private static ReentrantLock lock = new ReentrantLock(); //定义重入锁
- private static Condition condition = lock.newCondition(); //定义Condition
- @Override
- public void run() {
- try {
- lock.lock(); //调用 await() 需要先获得锁
- condition.await();
- System.out.println("这条线程继续执行!");
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- }
- public static void main(String[] args) throws InterruptedException {
- MyThread myThread = new MyThread();
- Thread t1 = new Thread(myThread);
- t1.start();
- Thread.sleep(2000);
- //通知 t1 线程继续执行
- lock.lock(); //调用 signal() 需要先获得锁
- condition.signal();
- lock.unlock(); //记得释放锁,不然线程没有继续执行
- }
- }
首先主线程开启副线程(t1)后,休眠2000毫秒。t1线程执行 run() 方法:获得锁后,调用 await() 释放锁并进入等待状态。
主线程被唤醒后,同样需要是获得锁,调用 signal() 唤醒线程但没有释放锁,所以要主动释放锁将其谦让给被唤醒的线程。
一旦线程被唤醒了,它会尝试着获取与之绑定的重入锁,成功获取后就可以继续执行。
- 在JDK内部,重入锁和 Condition 对象被广泛地使用。
三、允许多个线程同时访问:信号量(Semaphore)
信号量是对锁的扩展,可以指定多个线程同时访问某个资源
- 在构造信号量对象时,必须指定信号量的准入数。
- 申请信号量使用 acquire() 操作,离开时必须使用 release() 释放信号量(PS:若申请了但没有释放,会使可以进入临界区的线程数量越来越少,直到所有的线程均不可访问)。
四、倒计时器 CountDownLatch
这个工具用来控制线程等待,可以让某个线程等待直到倒计时结束,再执行。(PS:就像王者农药一样,需要所有玩家都准备好了才能开始游戏)
代码示例(来源于《实战Java高并发程序设计》,为了便于理解做了一点修改):
- public class MyThread implements Runnable {
- private static final CountDownLatch end = new CountDownLatch(5);
- private static final MyThread MY_THREAD = new MyThread();
- private static int count = 1;
- @Override
- public void run() {
- try {
- Thread.sleep(new Random().nextInt(5) * 1000);
- System.out.println("第 " + count + "条线程结束");
- count++;
- //倒计时减一
- end.countDown();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- public static void main(String[] args) throws InterruptedException {
- ExecutorService executorService = Executors.newFixedThreadPool(5);
- for (int i=0; i<5; i++){
- //线程池加入线程
- executorService.submit(MY_THREAD);
- }
- System.out.println("主线程开始执行");
- //主线程等待倒计时结束
- end.await();
- System.out.println("主线程重新执行");
- //关闭线程池
- executorService.shutdown();
- }
- }
注意注意:
1. 这里的ExecutorService
是线程池,在后面的学习中是非常重要的工具(有关其用法以后会学到,这里提前了解一下)。
2. 这里的 count 其实是线程不安全的,但因为并发量不高影响不大,而且 count 变量只是为了展示而已,其意义也不大。
输出结果如下:
- 主线程开始执行
- 第 1条线程结束
- 第 2条线程结束
- 第 3条线程结束
- 第 4条线程结束
- 第 5条线程结束
- 主线程重新执行
五、线程阻塞工具:LockSupport
LockSupport 是用来创建锁和其他同步类的基本线程阻塞,其特点:
- 可以在线程内任意位置堵塞线程。
- 不需要想获得对象的锁就可以执行;不会抛出 InterruptedException 异常。
- park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程(PS:不会引起 suspengd() 与 resume() 造成的死锁问题)。
- park() 方法支持中断,但不会抛出异常。所以需要我们使用 Thread.interrupted() 等方法来获得中断的标记。
为什么 park() 和 unpark() 不会引起 suspengd() 与 resume() 造成的死锁问题?
答:
首先,LockSupport 采用了类似于信号量的许可证机制。它为每一个线程准备了一个许可证(有且只有一个)。若 park() 可以获得许可证,会立刻返回,否则将处于堵塞状态。而 unpark() 会使一个许可证变为可用状态。所以说,即使 unpark() 发生在 park() 之前,也可以保证下一个 park() 操作立刻返回。
其次,处于 park() 挂起状态的线程,会明确地给出一个WAITING
状态,还会标记是 park() 引起的。
六、其他
- ReadWriteLock 读写锁:就是读读之间不阻塞(如果读操作次数远远大于写操作,读写锁就可以发挥最大功效,提升系统性能)。
- CyclicBarrier 循环栅栏:是 CountDownLatch 的升级版,可以接收一个参数 barrierAction,代表着一次计数完成后系统会执行的动作。
Java多线程的同步控制记录的更多相关文章
- Java多线程之同步集合和并发集合
Java多线程之同步集合和并发集合 不管是同步集合还是并发集合他们都支持线程安全,他们之间主要的区别体现在性能和可扩展性,还有他们如何实现的线程安全. 同步集合类 Hashtable Vector 同 ...
- Java多线程编程(同步、死锁、生产消费者问题)
Java多线程编程(同步.死锁.生产消费): 关于线程同步以及死锁问题: 线程同步概念:是指若干个线程对象并行进行资源的访问时实现的资源处理保护操作: 线程死锁概念:是指两个线程都在等待对方先完成,造 ...
- Java多线程synchronized同步
非线程安全问题 “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程问题”.也即是说,方法中的变量永远是线程安全的. 如果多个线程共同访问1个对象中的实例变量,则可能线程 ...
- 转载:浅谈Java多线程的同步问题【很好我就留下来,多分共享】
转载:http://www.cnblogs.com/phinecos/archive/2010/03/13/1684877.html#undefined 多线程的同步依靠的是对象锁机制,synchro ...
- 浅谈Java多线程的同步问题 【转】
多线程的同步依靠的是对象锁机制,synchronized关键字的背后就是利用了封锁来实现对共享资源的互斥访问. 下面以一个简单的实例来进行对比分析.实例要完成的工作非常简单,就是创建10个线程,每个线 ...
- Java多线程的同步机制(synchronized)
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...
- Java多线程 - 线程同步
多线程操作同一个对象时,容易引发线程安全问题.为了解决线程安全问题,Java多线程引入了同步监视器. 同步代码块 同步代码块语法格式如下: synchronized(obj){ //此处的代码即为同步 ...
- JAVA多线程线程同步问题
线程同步 在多线程的编程环境下,可能看着没有问题的代码在运行几千上万或者更多次后,出现了一些看着很奇怪的问题,出现这样的问题的原因就是可能会有两个或者更多个线程进入了同一块业务处理代码中导致了判断失效 ...
- JAVA - 多线程的同步
多线程的同步 1. 锁对象. 应用场景:当某个数据可能被其他线程修改时,给涉及到数据的方法上锁,保证同一时刻只有拥有这个锁的线程能访问该数据,其他要调用这个方法的线程被阻塞.注意:必须是不同线程访问同 ...
随机推荐
- django 项目中使用多数据库 multiple databases
假如在一个django项目中使用到了不只一个数据库, 其实这在大一点的工程中很常见,比如主从库 那么会涉及到如下一些东西 1, 定义 在settings中的DATABASE中定义会使用到的数据,比如除 ...
- 前向算法Python实现
前言 这里的前向算法与神经网络里的前向传播算法没有任何联系...这里的前向算法是自然语言处理领域隐马尔可夫模型第一个基本问题的算法. 前向算法是什么? 这里用一个海藻的例子来描述前向算法是什么.网上有 ...
- Go第七篇之规范的接口
接口本身是调用方和实现方均需要遵守的一种协议,大家按照统一的方法命名参数类型和数量来协调逻辑处理的过程. Go 语言中使用组合实现对象特性的描述.对象的内部使用结构体内嵌组合对象应该具有的特性,对外通 ...
- P3952 NOIP2017 时间复杂度
写了两三个小时,麻烦倒是不麻烦,要考虑清楚,想全了 只过了样例提交是不是傻,要自己造数据 数据不大可以用STL 建议自己刚一下,不看代码 #include <iostream> #incl ...
- springmvc-自定义消息转换器
最近的项目没有用到这个,先把自己自学跑通的例子先帖出来,供自己以后参考吧! 如有不对地方望指出! 一.自定义类实现AbstractHttpMessageConverter package com.dz ...
- 风景区的面积及道路状况分析问题 test
参考文献: https://wenku.baidu.com/view/b6aed86baf1ffc4ffe47ac92.html #include <bits/stdc++.h> us ...
- [JavaScript] - 7kyu
Johnny is a boy who likes to open and close lockers. He loves it so much that one day, when school w ...
- c#传统SqlTransaction事务和TransactionScope事务
事务有很多种,看了一些关于事务的问题,这里做下笔记····· 事务时单个的工作单位.如果某一事务成功,则在该事务中进行的所有数据更改均会提交,成为数据库中永久的组成部分.若果事务遇到错误,则必须取消或 ...
- java中年月日的加减法,年月的加减法使用
本文为博主原创,未经允许不得转载: java计算两个年月日之间相差的天数: public static int daysBetween(String smdate,String bdate) thro ...
- 【Coursera】Sixth Week(1)
Transport Layer 在学习完 Link Layer(Ethernet),Internetwork Layer(IP)之后,我们来到了TCP/IP协议簇的上半部分. Review:Magic ...