Java线程和多线程(十五)——线程的活性
当开发者在应用中使用了并发来提升性能的同时,开发者也需要注意线程之间有可能会相互阻塞。当整个应用执行的速度比预期要慢的时候,也就是应用没有按照预期的执行时间执行完毕。在本章中,我们来需要仔细分析可能会影响应用多线程的活性问题。
死锁
死锁的概念在软件开发者中已经广为熟知了,甚至普通的计算机用户也会经常使用这个概念,尽管不是在正确的状况下使用。严格来说,死锁意味着两个或者更多线程在等待另一个线程释放其锁定的资源,而请求资源的线程本身也锁定了对方线程所请求的资源。如下:
Thread 1: locks resource A, waits for resource B
Thread 2: locks resource B, waits for resource A
为了更好的理解问题,参考一下如下的代码:
public class Deadlock implements Runnable {
private static final Object resource1 = new Object();
private static final Object resource2 = new Object();
private final Random random = new Random(System.currentTimeMillis());
public static void main(String[] args) {
Thread myThread1 = new Thread(new Deadlock(), "thread-1");
Thread myThread2 = new Thread(new Deadlock(), "thread-2");
myThread1.start();
myThread2.start();
}
public void run() {
for (int i = 0; i < 10000; i++) {
boolean b = random.nextBoolean();
if (b) {
System.out.println("[" + Thread.currentThread().getName() +
"] Trying to lock resource 1.");
synchronized (resource1) {
System.out.println("[" + Thread.currentThread().
getName() + "] Locked resource 1.");
System.out.println("[" + Thread.currentThread().
getName() + "] Trying to lock resource 2.");
synchronized (resource2) {
System.out.println("[" + Thread.
currentThread().getName() + "] Locked
resource 2.");
}
}
} else {
System.out.println("[" + Thread.currentThread().getName() +
"] Trying to lock resource 2.");
synchronized (resource2) {
System.out.println("[" + Thread.currentThread().
getName() + "] Locked resource 2.");
System.out.println("[" + Thread.currentThread().
getName() + "] Trying to lock resource 1.");
synchronized (resource1) {
System.out.println("[" + Thread.
currentThread().getName() + "] Locked
resource 1.");
}
}
}
}
}
}
从上面的代码中可以看出,两个线程分别启动,并且尝试锁定2个静态的资源。但对于死锁,我们需要两个线程的以不同顺序锁定资源,因此我们利用随机实例选择线程要首先锁定的资源。
如果布尔变量b为true,resource1会锁定,然后尝试去获得resource2的锁。如果b是false,线程会优先锁定resource2,然而尝试锁定resource1。程序不用一会儿就会碰到死锁问题,然后就会一直挂住,直到我们结束了JVM才会结束:
[thread-1] Trying to lock resource 1.
[thread-1] Locked resource 1.
[thread-1] Trying to lock resource 2.
[thread-1] Locked resource 2.
[thread-2] Trying to lock resource 1.
[thread-2] Locked resource 1.
[thread-1] Trying to lock resource 2.
[thread-1] Locked resource 2.
[thread-2] Trying to lock resource 2.
[thread-1] Trying to lock resource 1.
在上面的执行中,thread-1持有了resource2的锁,等待resource1的锁,而线程thread-2持有了resource1的锁,等待resource2的锁。
如果我们将b的值配置true或者false的话,是不会碰到死锁的,因为执行的顺序始终是一致的,那么thread-1和thread-2请求锁的顺序始终是一致的。两个线程都会以同样的顺序请求锁,那么最多会暂时阻塞一个线程,最终都能够顺序执行。
大概来说,造成死锁需要如下的一些条件:
- 互斥:必须存在一个资源在某个时刻,仅能由一个线程访问。
- 资源持有:当锁定了一个资源的时候,线程仍然需要去获得另外一个资源的锁。
- 没有抢占策略:当某个线程已经持有了资源一段时间的时候,没有能够强占线程锁定资源的机制。
- 循环等待:在运行时必须存在两个或者更多的线程,相互请求对方锁定的资源。
尽管产生死锁的条件看起来较多,但是在多线程应用中存在死锁还是比较常见的。开发者可以通过打破死锁构成的必要条件来避免死锁的产生,参考如下:
- 互斥: 这个需求通常来说是不可避免的,资源很多时候确实只能互斥访问的。但是并不是总是这样的。当使用DBMS系统的时候,可能使用类似乐观锁的方式来代替原来的悲观锁的机制(在更新数据的时候锁定表中的一行)。
- 还有一种可行的方案,就是对资源持有进行处理,当获取了某一资源的锁之后,立刻获取其他所必须资源的锁,如果获取锁失败了,则释放掉之前所有的互斥资源。当然,这种方式并不是总是可以的,有可能锁定的资源之前是无法知道的,或者是废弃了的资源。
- 如果锁不能立刻获取,防止出现死锁的一种方式就是给锁的获取配置上一个超时时间。在SDK类中的
ReentrantLock就提供了类似超时的方法。 - 从上面的代码中,我们可以发现,如果每个线程的锁定资源的顺序是相同的,是不会产生死锁的。而这个过程可以通过将所有请求锁的代码都抽象到一个方法,然后由线程调用来实现。这就可以有效的避免死锁。
在一个更高级的应用中,开发者或许需要考虑实现一个检测死锁的系统。在这个系统中,来实现一些基于线程的监控,当前程获取一个锁,并且尝试请求别的锁的时候,都记录日志。如果以线程和锁构成有向图,开发者是能够检测到2不同的线程持有资源并且同时请求另外的阻塞的资源的。如果开发者可以检测,并能够强制阻塞的线程释放掉已经获取的资源,就能够自动检测到死锁并且自动修复死锁问题。
饥饿
线程调度器会决定哪一个处于RUNNABLE状态的线程会的执行顺序。决定一般是基于线程的优先级的;因此,低优先级的线程会获得较少的CPU时间,而高优先级的线程会获得较多的CPU时间。当然,这种调度听起来较为合理,但是有的时候也会引起问题。如果总是执行高优先级的线程,那么低优先级的线程就会无法获得足够的时间来执行,处于一种饥饿状态。因此,建议开发者只在真的十分必要的时候才去配置线程的优先级。
一个很复杂的线程饥饿的例子就是finalize()方法。Java语言中的这一特性可以用来进行垃圾回收,但是当开发者查看一下finalizer线程的优先级,就会发现其运行的优先级不是最高的。因此,很有可能finalize()方法跟其他方法比起来会执行更久。
另一个执行时间的问题是,线程以何种顺序通过同步代码块是没有定义的。当很多并行线程需要通过封装的同步代码块时,会有的线程等待的时间要比其它线程的时间更久才能进入同步代码快。理论上,他们可能永远无法进入代码块。这个问题可以使用公平锁的方案来解决。公平锁在选择下个线程的时候会考虑到线程的等待时间。其中一个公平锁的实现就是java.util.concurrent.locks.ReentrantLock:
如果使用ReentrantLock的如下构造函数:
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
传入true,那么ReentrantLock是一个公平锁,是会允许线程按挂起顺序来依次获取锁执行的。这样可以削减线程的饥饿,但是,并不能完全解决饥饿的问题,毕竟线程的调度是由操作系统调度的。所以,ReentrantLock类只考虑等待锁的线程,调度上是无法起作用的。举个例子,尽管使用了公平锁,但是操作系统会给低优先级的线程很短的执行时间。
Java线程和多线程(十五)——线程的活性的更多相关文章
- JAVA之旅(十五)——多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止
JAVA之旅(十五)--多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止 我们接着多线程讲 一.生产者和消费者 什么是生产者和消费者?我们 ...
- 201871010104-陈园园 《面向对象程序设计(java)》第十五周学习总结
201871010104-陈园园 <面向对象程序设计(java)>第十五周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ ...
- 201871010128-杨丽霞《面向对象程序设计(java)》第十五周学习总结
201871010128-杨丽霞<面向对象程序设计(java)>第十五周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ ...
- Java 设计模式系列(十五)观察者模式(Observer)
Java 设计模式系列(十五)观察者模式(Observer) Java 设计模式系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Java ...
- Java 设计模式系列(十五)迭代器模式(Iterator)
Java 设计模式系列(十五)迭代器模式(Iterator) 迭代器模式又叫游标(Cursor)模式,是对象的行为模式.迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象(interna ...
- 201271050130-滕江南-《面向对象程序设计(java)》第十五周学习总结
201271050130-滕江南-<面向对象程序设计(java)>第十五周学习总结 博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.c ...
- 201871010111-刘佳华《面向对象程序设计(java)》第十五周学习总结
201871010111-刘佳华<面向对象程序设计(java)>第十五周学习总结 实验十三 Swing图形界面组件(二) 实验时间 2019-12-6 第一部分:理论知识总结 5> ...
- 201871010123-吴丽丽《面向对象程序设计(Java)》第十五周学习总结
201871010123-吴丽丽<面向对象程序设计(Java)>第十五周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ ...
- 201871010105-曹玉中《面向对象程序设计(java)》第十五周学习总结
201871010105-曹玉中<面向对象程序设计(java)>第十五周学习总结 项目 内容 这个作业属于哪个过程 https://www.cnblogs.com/nwnu-daizh/ ...
- 201871010107-公海瑜《面向对象程序设计(java)》第十五周学习总结
201871010107-公海瑜<面向对象程序设计(java)>第十五周学习总结 项目 内容 这个作业属于 ...
随机推荐
- python 标准类库-并行执行之subprocess-子进程管理
标准类库-并行执行之subprocess-子进程管理 by:授客QQ:1033553122 1.使用subprocess模块 以下函数是调用子进程的推荐方法,所有使用场景它们都能处理.也可用Popen ...
- The value of ESP was not properly saved across a function call 快速解决
The value of ESP was not properly...快速解决 今天遇到这个问题,真的是非常头疼,期间电脑居然崩掉一次.所以,分享一下解决办法. 如果是:类定义的时候,新添加了属性, ...
- 章节七、4-Sets
一.set中不允许存在相同的元素 package ZangJie7; import java.util.ArrayList; import java.util.HashSet; import java ...
- git 入门教程之安装 git
安装 git git 目前支持 Linux/Unix.Solaris.Mac和 Windows 平台上运行,根据自身环境选择安装. Linux 系统 linux 系统安装软件大致有两种途径,一种是利用 ...
- weblogic系列漏洞整理 -- 5. weblogic SSRF 漏洞 UDDI Explorer对外开放 (CVE-2014-4210)
目录 五. weblogic SSRF 漏洞 UDDI Explorer对外开放 (CVE-2014-4210) 1. 利用过程 2. 修复建议 一.weblogic安装 http://www.cnb ...
- 卸载时候出现: windows installer 程序有问题。此安装需要的dll不能运行 的一个解决方法
卸载时候出现: windows installer 程序有问题.此安装需要的dll不能运行 安装Your Uninstaller来卸载
- C#-结构体(十)
结构体概念 在C#中,结构体是值类型,一般适用于表示类似Point.Rectangle.Color的对象 值类型能够降低对堆的管理.使用.降低垃圾回收,表现出更好的性能.可是值类型也有不好的一面.会涉 ...
- Vue2 学习笔记5
文中例子代码请参考github watch属性的使用 考虑一个问题:想要实现 名 和 姓 两个文本框的内容改变,则全名的文本框中的值也跟着改变:(用以前的知识如何实现???) 监听data中属性的改变 ...
- 【ABAP CDS系列】ABAP CDS中的系统信息
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP CDS系列]ABAP CDS中的系统 ...
- Linux安装Python3后,如何使用pip命令
系统环境:CentOS7.4 已安装好Python3.6.5 Python3.6.5自带pip 使用pip安装第三方库,可运行指令,例如安装paramiko库: python -m pip insta ...