安全性问题
毫无疑问,线性安全是很重要的。那么如何判断一个类是线性安全的呢?
当多个线程访问某个类时,不管运行环境采用何种调度方式或者这些线程将如何交替执行,并且在主调用代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线性安全的。
解决线程安全性问题可以用前面提到的加锁机制,包括类内置的锁和显式锁。
活跃性问题
死锁
一个具体死锁的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public class LeftRightDeadLock { private final Object left = new Object(); private final Object right = new Object(); public void leftRight() throws InterruptedException { synchronized (left) { Thread.sleep(1000); synchronized (right) { System.out.println("leftRight..."); } } } public void rightLeft() throws InterruptedException { synchronized (right) { Thread.sleep(1000); synchronized (left) { System.out.println("rightLeft..."); } } } public static void main(String[] args) { LeftRightDeadLock lr = new LeftRightDeadLock(); new AThread(lr).start(); new BThread(lr).start(); } } class AThread extends Thread{ private LeftRightDeadLock lr; public AThread(LeftRightDeadLock lr) { this.lr = lr; } @Override public void run(){ try { lr.leftRight(); } catch (InterruptedException e) { e.printStackTrace(); } } } class BThread extends Thread{ private LeftRightDeadLock lr; public BThread(LeftRightDeadLock lr) { this.lr = lr; } @Override public void run(){ try { lr.rightLeft(); } catch (InterruptedException e) { e.printStackTrace(); } } }
|
运行时候将什么也没有输出,因为两个线程都在等待对方释放锁。
通常解决的方法有下面两种:
按照顺序加锁
:例如将上面的加锁顺序改为都是先对right加锁,然后才对left加锁。
使用定时锁,避免进入无限循环
。例如可以显式使用Lock类中的定时tryLock功能来代替内置锁机制。
饥饿
当线程由于无法访问它所需要的资源而不能继续执行时,就发生
饥饿
。
引发饥饿的最常见的资源就是CPU的时钟周期。如果在Java程序中对线程的优先级使用不当,或者在持有锁时执行一些无法结束的方法(例如无限循环),那么也可能导致饥饿,因为其他需要这个锁的线程将无法得到它。
通常,我们尽量不要改变线程的优先级,因为改变了优先级,程序的行为将和平台有关,并且会导致发生饥饿问题的风险。
活锁
活锁是另外一种形式的活跃性问题。该问题虽然不会阻塞线程,但是也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败。
当多个相互协作的线程对于彼此进行响应从而修改各自的状态,并使得任何一个线程都无法继续执行时,就发生了活锁。这就像两个过于礼貌的人在半路上相遇,他们彼此都让出了对方的路,然而又在另外一条路上遇上了。因此他们就这样反复地避让下去。
要解决这种活锁问题,需要在重试机制中引入随机性。通过等待随机长度的时间和回退可以有效地避免活锁的产生。
性能问题
线程的最主要的目的是提高程序的运行性能。
尽管使用多个线程的目标是提升整体性能,但与单线程的方法相比,使用多个线程总会引入一些额外的性能开销。
造成这些开销的操作包括:线程之间的协调(例如加锁、触发信号以及内存同步)、增加的上下文切换、线程的创建和销毁、以及线程的调度等。
通常为了减少这些开销,提高整体的吞吐量和伸缩性,可以通过下面的方法:
减少锁的持有时间
。
降低锁的粒度
:例如可以使用同步块代替同步方法,或者使用显式锁来代替内置锁。
使用非独占的锁或者非阻塞的锁来代替锁
:例如并发容器、volatile变量、原子变量等。