1. 死锁
多个线程各自占用一些共享资源,并且互相等待其它线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步代码块同时拥有两个以上对象的锁时,就有可能发生死锁的问题。
案例:女生化妆。化妆品有:镜子、口红。灰姑娘先选择口红,再选择镜子;白雪公主先选择镜子,再选择口红
// 口红
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 + "拿到了口红的锁");
}
}
}
}
运行结果如下:
2.锁 Lock
从 JDK 1.5 开始,Java 提供了更强大的线程同步机制:通过显示定义同步锁对象来实现同步。同步锁使用 Lock
对象充当。
java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象。
ReentrantLock
类实现了 Lock
接口,它拥有与 synchronized
相同的并发性和内存语义。在实现线程安全的控制中,比较常用的是 ReentrantLock
,可以显示地加锁、释放锁。
Lock
与 synchronized
的比较:
- Lock 显示锁,手动加锁、解锁;用 Lock,JVM 将花费较少的时间来调度线程,性能更好
- 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();
}
}