当开发者在应用中使用了并发来提高性能的同时,开发者也须要注意线程之间有可能会相互阻塞。当整个应用执行的速度比预期要慢的时候,也就是应用没有按照预期的执行时间执行完毕。在本章中,咱们来须要仔细分析可能会影响应用多线程的活性问题。java
死锁的概念在软件开发者中已经广为熟知了,甚至普通的计算机用户也会常用这个概念,尽管不是在正确的情况下使用。严格来讲,死锁意味着两个或者更多线程在等待另外一个线程释放其锁定的资源,而请求资源的线程自己也锁定了对方线程所请求的资源。以下:sql
Thread 1: locks resource A, waits for resource B
Thread 2: locks resource B, waits for resource A
为了更好的理解问题,参考一下以下的代码:bash
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个静态的资源。但对于死锁,咱们须要两个线程的以不一样顺序锁定资源,所以咱们利用随机实例选择线程要首先锁定的资源。markdown
若是布尔变量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
请求锁的顺序始终是一致的。两个线程都会以一样的顺序请求锁,那么最多会暂时阻塞一个线程,最终都可以顺序执行。dom
大概来讲,形成死锁须要以下的一些条件:函数
尽管产生死锁的条件看起来较多,可是在多线程应用中存在死锁仍是比较常见的。开发者能够经过打破死锁构成的必要条件来避免死锁的产生,参考以下:post
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
类只考虑等待锁的线程,调度上是没法起做用的。举个例子,尽管使用了公平锁,可是操做系统会给低优先级的线程很短的执行时间。