多个线程各自占用一些共享资源,并且互相等待其它线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步代码块同时拥有两个以上对象的锁时,就有可能发生死锁的问题。
案例:女生化妆。化妆品有:镜子、口红。灰姑娘先选择口红,再选择镜子;白雪公主先选择镜子,再选择口红
// 口红 class Lipstick {} // 镜子 class Mirror {} class Makeup extends Thread { private static Lipstick lipstick = new Lipstick(); private static Mirror mirror = new Mirror(); private int choice; private String girlName; public Makeup(int choice, String girlName) { this.choice = choice; this.girlName = girlName; } @Override public void run() { try { makeup(); } catch (Exception e) { e.printStackTrace(); } } // 化妆 private void makeup() throws Exception{ if (0 == choice) { synchronized (lipstick) { System.out.println(this.girlName + "拿到了口红的锁"); Thread.sleep(1000); synchronized (mirror) { System.out.println(this.girlName + "拿到了镜子的锁"); } } } else { synchronized (mirror) { System.out.println(this.girlName + "拿到了镜子的锁"); Thread.sleep(2000); synchronized (lipstick) { System.out.println(this.girlName + "拿到了口红的锁"); } } } } }
测试:
// 死锁 多个线程互相持有对方需要的资源,然后形成僵持 public class DeadLock { public static void main(String[] args) { Makeup g1 = new Makeup(0, "灰姑凉"); Makeup g2 = new Makeup(1, "白雪公主"); g1.start(); g2.start(); } }
运行后:
程序一直处于死锁状态。
那么,如何解除这种状态呢?
class Makeup extends Thread { ... // 化妆 private void makeup() throws Exception{ if (0 == choice) { synchronized (lipstick) { System.out.println(this.girlName + "拿到了口红的锁"); Thread.sleep(1000); } synchronized (mirror) { System.out.println(this.girlName + "拿到了镜子的锁"); } } else { synchronized (mirror) { System.out.println(this.girlName + "拿到了镜子的锁"); Thread.sleep(2000); } synchronized (lipstick) { System.out.println(this.girlName + "拿到了口红的锁"); } } } }
运行结果如下:
从 JDK 1.5 开始,Java 提供了更强大的线程同步机制:通过显示定义同步锁对象来实现同步。同步锁使用 Lock
对象充当。
java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象。
ReentrantLock
类实现了 Lock
接口,它拥有与 synchronized
相同的并发性和内存语义。在实现线程安全的控制中,比较常用的是 ReentrantLock
,可以显示地加锁、释放锁。
Lock
与 synchronized
的比较:
案例:使用 Lock 实现线程安全地买票
class TestLock2 implements Runnable { private int ticketNums = 10; // 可重入锁 private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { lock.lock(); try { if (ticketNums <= 0) { break; } try { Thread.sleep(1000); System.out.println(ticketNums--); } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlock(); } } } }
测试:
public class TestLock { public static void main(String[] args) { TestLock2 testLock2 = new TestLock2(); new Thread(testLock2).start(); new Thread(testLock2).start(); new Thread(testLock2).start(); } }