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. 锁对象. 应用场景:当某个数据可能被其他线程修改时,给涉及到数据的方法上锁,保证同一时刻只有拥有这个锁的线程能访问该数据,其他要调用这个方法的线程被阻塞.注意:必须是不同线程访问同 ...
随机推荐
- JavaScript 实现省市二级联动
JavaScript 实现省市二级联动 版权声明:未经授权,严禁转载! 案例代码 <style> .hide { display: none; } </style> <s ...
- Android 基础知识点(一)
- 20165310java_blog_week7
2165310 <Java程序设计>第7周学习总结 教材学习内容总结 通过JDBC管理数据库 原理图: 链接数据库方式 - `Connection getConnection(java.l ...
- CDC画图
CDC* pdc: CRect rcBounds: 1. 画直线 pdc->MoveTo(rcBounds.TopLeft());//将画笔移动到左上角这个点,使用这个点作为起点画图 pdc-& ...
- 使用fragment添加底部导航栏
切记:fragment一定要放在framlayout中,不然不会被替换完全(就是切换之后原来的fagment可能还会存在) main.xml <LinearLayout xmlns:androi ...
- 完美解决vim在终端不能复制的问题
以前 用xshell,或者其他工具ssh到远程服务器,vim不能复制,搜索说是vim的 -xterm_clipboard没有开启. 今天发现,至少鼠标复制是不需要这个东东的! 在stackoverfl ...
- SpringBoot中使用mybatis-generator自动生产
步骤: 1.在pom.xml中添加插件配置 <plugin> <groupId>org.mybatis.generator</groupId> <artifa ...
- HDU 5992 Finding Hotels(KD树)题解
题意:n家旅店,每个旅店都有坐标x,y,每晚价钱z,m个客人,坐标x,y,钱c,问你每个客人最近且能住进去(非花最少钱)的旅店,一样近的选排名靠前的. 思路:KD树模板题 代码: #include&l ...
- POJ 1845 Sumdiv(求因数和 + 逆元)题解
题意:给你a,b,要求给出a^b的因子和取模9901的结果. 思路:求因子和的方法:任意A = p1^a1 * p2^a2 ....pn^an,则因子和为sum =(1 + p1 + p1^2 + . ...
- 第五章 消息摘要算法--MAC
注意:本节内容主要参考自<Java加密与解密的艺术(第2版)>第6章“验证数据完整性--消息摘要算法” 5.1.mac(又称为Hmac) 原理:在md与sha系列算法的基础上加入了密钥,是 ...