避免死锁
- 死锁
由于某种原因,锁一直没释放,后续需要获取锁的线程都将处于等待锁的状态,这样程序就卡死。
导致死锁的原因不多
- 获取锁后没释放,有经验的程序员很少犯这种错误,即使出现也很容易解决
- 锁的重入
看下面代码:
public void visitShareResWithLock() { lock.lock(); // 获取锁 try { lock.lock(); // 再次获取锁,会导致死锁吗? } finally { lock.unlock(); }
当前的线程获取到了锁lock,然后在持有这把锁的情况下,再次去尝试获取这把锁,这样会导致死锁吗?
不一定。会不会死锁取决于,你获取的这把锁它是不是可重入锁。如果是可重入锁,那就没有问题,否则就会死锁。
大部分编程语言都提供了可重入锁,若无特别要求,尽量使用可重入锁。因为若程序复杂,调用栈很深,很多情况下,当需要获取一把锁时,你不太好判断在n层调用之外的某个地方,是不是已经获取过这把锁,这时,获取可重入锁就有必要。
最后一种死锁的情况是最复杂的,也是最难解决的。如果你的程序中存在多把锁,就有可能出现这些锁互相锁住的情况。
模拟最简单最典型的死锁情况。在这个程序里面,我们有两把锁:lockA和lockB,然后我们定义了两个线程,这两个线程反复地去获取这两把锁,然后释放。
程序执行一会儿就卡住了,发生死锁。
他们获取锁的顺序不一样。
第一个线程,先获取lockA,再获取lockB;
第二个线程正好相反,先获取lockB,再获取lockA。
这最简单的两把锁两个线程死锁的情况,还可以分析清楚,如果你的程序中有十几把锁,几十处加锁解锁,几百线程,如果出现死锁你还能分析清楚是什么情况吗?
避免死锁
程序尽量少用锁
同把锁,加锁和解锁必须放在同一方法
尽量避免同时持有多把锁,即持有一把锁时,又去获取另外一把锁
若需要持多把锁,注意加解锁顺序,解锁顺序要和加锁顺序相反
给你程序中所有的锁排一个顺序,在所有需要加锁的地方,按照同样的顺序加解锁。
如果两个线程都按照先获取lockA再获取lockB的顺序加锁,就不会产生死锁。
使用读写锁
共享数据,如果某方法访问它时,只读取,并不更新,就不需要加锁?
还是需要的,因为如果一个线程读时,另外一个线程同时在更新,那么你读数据有可能是更新到一半的。
所以,无论只读还是读写访问,都是需要加锁的。
锁虽然解决安全问题,但牺牲性能无法并发。
若无线程在更新,即使多线程并发读,也没问题。大部分情况下,数据读要远多于写,所以,我们希望的是:
读可并发执行。
写的同时不能并发读,也不能并发写。
这就兼顾性能和安全。读写锁就为这此而设计。
Java读写锁实例
ReadWriteLock rwlock = new ReentrantReadWriteLock(); public void read() { rwlock.readLock().lock(); try { // 在这儿读取共享数据 } finally { rwlock.readLock().unlock(); } } public void write() { rwlock.writeLock().lock(); try { // 在这儿更新共享数据 } finally { rwlock.writeLock().unlock(); } }
需要读数据的时候,我们获取读锁,不是互斥锁,read()方法可多线程并行执行,这使读性能很好。
写数据,获取写锁,当一个线程持有写锁,其他线程既无法获取读锁,也不能获取写锁,从而保护共享数据。
如此读写锁就兼顾了性能和安全。
在Java中实现一个try-with-lock呢?
java7开始io就有try-with-resource。 可以利用这一个特性,来说实现,自动释放。 代码如下: public class AutoUnlockProxy implements Closeable { private Lock lock; public AutoUnlockProxy(Lock lock) { this.lock = lock; } @Override public void close() throws IOException { lock.unlock(); System.out.println("释放锁"); } public void lock() { lock.lock(); } public void tryLock(long time, TimeUnit unit) throws InterruptedException { lock.tryLock(time, unit); } public static void main(String[] args) { try (AutoUnlockProxy autoUnlockProxy = new AutoUnlockProxy(new ReentrantLock())) { autoUnlockProxy.lock(); System.out.println("加锁了"); } catch (IOException e) { e.printStackTrace(); } } }