java多线程系列5-死锁与线程间通信
这篇文章介绍java死锁机制和线程间通信
死锁
死锁:两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。
同步代码块的嵌套案例
public class MyLock { // 创建两把锁对象 public static final Object objA = new Object(); public static final Object objB = new Object(); } public class DieLock extends Thread { private boolean flag; public DieLock(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { synchronized (MyLock.objA) { System.out.println("if objA"); synchronized (MyLock.objB) { System.out.println("if objB"); } } } else { synchronized (MyLock.objB) { System.out.println("else objB"); synchronized (MyLock.objA) { System.out.println("else objA"); } } } } } public class DieLockDemo { public static void main(String[] args) { DieLock dl1 = new DieLock(true); DieLock dl2 = new DieLock(false); dl1.start(); dl2.start(); } }
避免死锁
有很多方针可供我们使用来避免死锁的局面:
- 避免嵌套封锁:这是死锁最主要的原因的,如果你已经有一个资源了就要避免封锁另一个资源。如果你运行时只有一个对象封锁,那是几乎不可能出现一个死锁局面的。例如,这里是另一个运行中没有嵌套封锁的run()方法,而且程序运行没有死锁局面,运行得很成功。
- 只对有请求的进行封锁:你应当只想你要运行的资源获取封锁,比如在上述程序中我在封锁的完全的对象资源。但是如果我们只对它所属领域中的一个感兴趣,那我们应当封锁住那个特殊的领域而并非完全的对象。
- 避免无限期的等待:如果两个线程正在等待对象结束,无限期的使用线程加入,如果你的线程必须要等待另一个线程的结束,若是等待进程的结束加入最好准备最长时间。
线程间通信
1、Condition newCondition()
返回绑定到此 Lock
实例的新 Condition
实例。在等待条件前,锁必须由当前线程保持。调用Condition.await()
将在等待前以原子方式释放锁,并在等待返回前重新获取锁。
实现注意事项
Condition
实例的具体操作依赖于 Lock
实现,并且该实现必须对此加以记录。
返回:用于此 Lock
实例的新 Condition
实例
2、public interface Condition
Condition
将 Object
监视器方法(wait
、notify
和 notifyAll
)分解成截然不同的对象,以便通过将这些对象与任意 Lock
实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock
替代了 synchronized
方法和语句的使用,Condition
替代了 Object 监视器方法的使用。
条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait
做的那样。
Condition
实例实质上被绑定到一个锁上。要为特定 Lock
实例获得 Condition
实例,请使用其 newCondition()
方法。
方法摘要 | |
---|---|
void |
await() 造成当前线程在接到信号或被中断之前一直处于等待状态。 |
boolean |
await(long time, TimeUnit unit) 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 |
long |
awaitNanos(long nanosTimeout) 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 |
void |
awaitUninterruptibly() 造成当前线程在接到信号之前一直处于等待状态。 |
boolean |
awaitUntil(Date deadline) 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。 |
void |
signal() 唤醒一个等待线程。 |
void |
signalAll() 唤醒所有等待线程。 |
示例:假设创建并启动两个任务,一个用来向账户存款,另一个从同一个账户取款。当取款数额大于账户余额的时,取款线程必须等待。不管什么时候,只要向账户新存了一笔资金,存款线程必须通知提款线程重新尝试。如果余额仍为达到取款数额、提款线程必须继续等待新的存款。
为了同步这些操作,使用一个由条件的锁newDeposit(即增加到账户的新存款)。如果余额小于取款数额,提款任务将等待newDeposit条件。当存款任务给账户增加资金时,存款任务唤醒等待中的提款任务在次尝试。
代码如下:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreadCooperation { private static Account account = new Account(); public static void main(String[] args) { System.out.println("Thread 1\t\tThread 2\t\tBalance"); //创建一个包含两个线程的线程池 ExecutorService executor = Executors.newFixedThreadPool(2); executor.execute(new DepositTask()); executor.execute(new WithDrawTask()); executor.shutdown(); } //向账户中存钱的任务 public static class DepositTask implements Runnable { @Override public void run() { try { while(true) { account.deposit((int)(Math.random() * 10) + 1); Thread.sleep(1000); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //从账户中取款的任务 public static class WithDrawTask implements Runnable { @Override public void run() { while(true) { account.withdraw((int)(Math.random() * 10) + 1); } } } public static class Account { // 创建一个锁 private static Lock lock = new ReentrantLock(); // 创建一个Condition private static Condition newDeposit = lock.newCondition(); private int balance = 0; public int getBalance() { return balance; } public void withdraw(int amount) { lock.lock(); try { while (balance < amount) { System.out.println("\t\t\tWait for a deposit"); newDeposit.await(); } balance -= amount; System.out.println("\t\t\tWithdraw " + amount + "\t\t" + getBalance()); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 释放锁 } } public void deposit(int amount) { lock.lock(); try { balance += amount; System.out.println("Deposit " + amount + "\t\t\t\t\t" + getBalance()); newDeposit.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 释放锁 } } } }