您的位置:首页 > 编程语言 > Java开发

Java并发编程之活跃性、性能与测试

2018-02-23 22:18 405 查看

避免活跃性危险

一、死锁:在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么这两个线程将永远等待下去,这种情况下就是最简单的死锁。
1.锁顺序死锁:两个线程试图以不同的顺序来获得相同的锁,比如线程A线锁住锁L,尝试锁住M,而线程B线锁住了M,尝试锁住L,这样就发生死锁了,如果按照相同的顺序来请求锁,那么就不会出现循环的加锁依赖性。比如都是按照现锁住L后锁住M的顺序来获得锁,那就不会死锁。
2.动态的锁顺序死锁:比如转账的方法transferMoney(myAccount,yourAccount,10)和transferMoney(yourAccount,myAccount,10),同时执行这两个转账,X转给Y,Y转给X,如果执行的时序不当,也会发生死锁。而且这种死锁我们无法控制参数的顺序,所以只能定义锁的顺序。而在制定锁的顺序上,我们可以使用System.identityHashCode方法,该方法将返回由Object.hashCode返回的值。在极少的情况下,会有相同的散列值,在这种时候,我们就必须使用“加时赛”锁,在获得锁之前,线获得这个“加时赛”锁,从而保证每次只有一个线程以位置的顺序获得这个锁。
3.在协作对象之间发生的死锁:比如说两个类Taxi出租车和Dispacher车队,Taxi有更新地点的方法setLocation,车队显示获取每个车的位置的方法getImage,当出租车到达某个位置要使用setLocation,并且需要告诉车队,那么它需要线活动Taxi的锁,然后获得Dispacher的锁,而车队显示每个车的位置的方法需要先获得Dispacher的锁,再获得Taxi的锁,这就有可能产生死锁。而这个死锁比较难以找到,可以使用开放调用来解决,也就是在调用某个方法时不需要持有锁。
4.资源死锁:当多个线程在相同资源集合上等待时,也会发生死锁。例如资源池的死锁,还有一种资源死锁就是线程饥饿死锁。
检查死锁的步骤:首先找出在什么地方获取多个锁,然后对所有这些实例进行全局分析,从而确保它们在整个程序中获取锁的顺序都保持一致。尽可能的使用开放调用,这能极大的简化分析过程。如果所有调用都是开放调用的,那么就通过代码审查就可以找到。
支持定时的锁:利用显式锁,可以指定一个超时时限,在等待超过时间后tryLock会返回一个失败信息。用这种方法来消除死锁发生的可能性。

JVM会通过线程转储来帮助识别死锁的发生。
二、饥饿:放线程无法访问它所需要的资源而不能继续的时候,就发生了饥饿。引发饥饿最常见的就是CPU时钟周期,还有执行一些无法结束的结构比如死循环之类的。
三、糟糕的响应性
四、活锁:尽管不会阻塞线程,但是也不能继续执行。因为线程将不断重复执行相同的操作,而且总会失败。活锁通常发生在事务消息的应用程序中:如果不能成功的处理某个消息,那么消息处理机制将回滚整个事务,并将它重新放到队列的开头,如果消息处理器在处理某种特定类型的消息时存在错误导致它失败,那么每当这个消息从队列中取出并传递到存在错误的处理器时,都会发生事务回滚。由于这条消息又被放到队列开头,因此处理器将被反复调用,并返回相同的结果。
要解决活锁问题,需要在重试机制中加入随机性。也就是让一直循环在做某个操作的事务进行一段随机性等待,就可以解决活锁问题。
这里补充一下显示锁的知识:
在java5.0以前协调堆共享对象的访问时可以使用synchronized和volatile,在这之后添加了一种新的机制:ReentrantLock。
Lock接口中定义了一组抽象的加锁操作,与内置锁不同,Lock提供了一种无条件的、课轮询的、定时的以及课中断的锁获取操作。ReentrantLock实现了Lock接口,并且提供了跟synchronized一样的互斥性和内存可见性。ReentrantLock跟synchronized一样都有进入同步代码块和退出同步代码块的内存语义,还提供给了一样的可重入加锁语义。
ReentrantLock使用比synchronized复杂:需要在finally块中释放锁,如果没有释放锁,将很难追踪到最初发生错误的位置,因为没有记录释放锁的位置和时间。

可轮询的与可定时的锁获取模式是由tryLock方法实现的,而他们就避免了死锁的发生。定时的锁获取操作能在带有时间限制的操作中使用独占锁,可中断的锁获取操作同样能在可取消的操作中使用加锁。lockInterruptibly方法能够在获取锁的同时保持对中断的响应。
ReentrantLock的构造函数中提供了公平锁和非公平锁,在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许插队:当一个线程请求非公平锁时,如果在发出请求的同时,该锁的状态变为可用,则这个线程跳过队列直接获得这个锁。非公平锁的性能比公平锁高。

synchronized的局限性:1.无法中断一个正在等待获取锁的线程,2.无法在请求一个锁时无限的等待下去。3.无法实现非阻塞结构的加锁规则。

ReentrantLock,提供了定时的锁等待、可中断的锁等待、公平性,以及实现非块结构的加锁,但是它的危险性比synchronized要高,比如忘记finally中unlock,而且使用synchronized,线程转储中能给出哪些调用帧中获得了哪些锁,并且能够检测和识别发生死锁的线程,但是使用ReetrantLock的话,JVM就没有这个支持了,所以还是使用synchronized,当遇到内置锁不合适的时候,才用到ReentrantLock。

读写锁:允许多个读操作同时进行,但每次只允许一个写操作。和Lock一样,ReadWriteLock也有很多不同的实现方式。
ReentrantReadWriteLock为写入锁和读取锁提供了可重入的加锁语义,在构造时也可以选择公平性,在公平锁中,等待时间最长的线程将优先获得锁。如果这个锁由读线程持有,而另一个线程请求写入锁,那么其他线程都不能获得读取锁,直到写线程使用完并且释放了写入锁。在非公平的锁中,线程获取访问许可是不确定的。写线程降级为读线程是可以的,但是从读线程升级为写线程是不可以的。(这样会导致死锁)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java 并发编程