死锁
源码来源:Java死锁及如何解决死锁
是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象
一 死锁的产生有四个必要的条件
1.互斥使用,即当资源被一个线程占用时,别的线程不能使用
2.不可抢占,资源请求者不能强制从资源占有者手中抢夺资源,资源只能由占有者主动释放
3.请求和保持,当资源请求者在请求其他资源的同时保持对原因资源的占有
4.循环等待,多个线程存在环路的锁依赖关系而永远等待下去,例如T1占有T2的资源,T2占有T3的资源,T3占有T1的资源,这种情况可能会形成一个等待环路
这样记忆没有意义,所以换一种记忆的方式,锁的特点
加锁的资源只能同时被一个线程使用 -> 多个线程不能使用同一个资源
加锁的资源所在线程就算阻塞,也不会释放锁 -> 不释放已占有资源
加锁的资源所在线程不能强行释放锁 -> 资源未使⽤完之前,不能被强⾏剥夺
多个锁嵌套使用,容易造成循环等待资源,产生死锁
二 死锁的举例
死锁举例1
/**
* @author Tony Stark
* @create 2022-08-25 19:44
* 死锁,th1拿到同步监视器o1,就是锁o1,接着需要拿到锁o2;
* 同时,th2需要拿到o2,再拿到o1。两个线程会相互僵持,形成死锁
*
* 下面代码有一定概率不形成死锁,某一个线程迅速连续抢到时间片然后执行完毕,将锁释放给另一个线程
* 所以,synchornized谨慎嵌套使用。
*/
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run(){
synchronized (o1){
System.out.println("线程1拿到了o1锁,准备拿o2");
synchronized (o2){
System.out.println("线程1拿到了o2,结束");
}
}
}
}
class MyThread2 extends Thread{
Object o1;
Object o2;
public MyThread2(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run(){
synchronized (o2){
System.out.println("线程2拿到了o2锁,准备拿o1");
synchronized (o1){
System.out.println("线程2拿到了o1锁,结束");
}
}
}
}
public class DeadLock {
public static void main(String[] args){
Object o1 = new Object();
Object o2 = new Object();
MyThread1 th1 = new MyThread1(o1,o2);
MyThread2 th2 = new MyThread2(o1,o2);
th1.start();
th2.start();
}
}
死锁举例2(区分主线程和线程)
public class NormalDeadLock {
//定义两个对象锁
private static Object valueFirst = new Object();//第一个锁
private static Object valueSecond = new Object();//第二个锁
//先拿第一个锁,再拿第二个锁
private static void fisrtToSecond() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (valueFirst) {
System.out.println(threadName+" get first");
Thread.sleep(100);
synchronized (valueSecond) {
System.out.println(threadName+" get second");
}
}
}
//先拿第二个锁,再拿第一个锁
private static void SecondToFisrt() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (valueSecond) {
System.out.println(threadName+" get second");
Thread.sleep(101);
synchronized (valueFirst) {
System.out.println(threadName+" get first");
}
}
}
//执行先拿第二个锁,再拿第一个锁
private static class TestThread extends Thread{
private String name;
public TestThread(String name) {
this.name = name;
}
public void run(){
Thread.currentThread().setName(name);
try {
SecondToFisrt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread.currentThread().setName("TestDeadLock");
TestThread testThread = new TestThread("SubTestThread");
testThread.start();
try {
fisrtToSecond();//先拿第一个锁,再拿第二个锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
死锁举例3,动态死锁,经典的转账案例
/**
*类说明:银行转账动作接口
*/
public interface ITransfer {
/**@param from 转出账户
* @param to 转入账户
* @param amount 转账金额
* @throws InterruptedException
*/
void transfer(UserAccount from, UserAccount to, int amount) throws InterruptedException;
}
/**
*类说明:用户账户的实体类
*/
public class UserAccount {
private final String name;//账户名称
private int money;//账户余额
//显示锁
private final Lock lock = new ReentrantLock();
public Lock getLock() {
return lock;
}
public UserAccount(String name, int amount) {
this.name = name;
this.money = amount;
}
public String getName() {
return name;
}
public int getAmount() {
return money;
}
@Override
public String toString() {
return "UserAccount{" +
"name='" + name + '\'' +
", money=" + money +
'}';
}
//转入资金
public void addMoney(int amount){
money = money + amount;
}
//转出资金
public void flyMoney(int amount){
money = money - amount;
}
}
/**
*类说明:不安全的转账动作的实现
*解释:from to都是用户UserAccount类,假设有账户F1,F2,F3,T1,T2,T3
* 比如线程1(F1,T1,100),线程2(F1,T2,200),先锁住from,两个线程同时进来没问题。因为都是从F1转出。在锁to,保证线程安全。
*/
public class TrasnferAccount implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
synchronized (from){//先锁转出
System.out.println(Thread.currentThread().getName()
+" get"+from.getName());
Thread.sleep(100);
synchronized (to){//再锁转入
System.out.println(Thread.currentThread().getName()
+" get"+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
/**
*类说明:模拟支付公司转账的动作
*/
public class PayCompany {
/*执行转账动作的线程*/
private static class TransferThread extends Thread{
private String name;//线程名字
private UserAccount from;
private UserAccount to;
private int amount;
private ITransfer transfer; //实际的转账动作,采用多态,父类引用指向子类对象
public TransferThread(String name, UserAccount from, UserAccount to,
int amount, ITransfer transfer) {
this.name = name;
this.from = from;
this.to = to;
this.amount = amount;
this.transfer = transfer;
}
public void run(){
Thread.currentThread().setName(name);
try {
transfer.transfer(from,to,amount);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
PayCompany payCompany = new PayCompany();
UserAccount zhangsan = new UserAccount("zhangsan",20000);
UserAccount lisi = new UserAccount("lisi",20000);
ITransfer transfer = new TransferThread();//多态
TransferThread zhangsanToLisi = new TransferThread("zhangsanToLisi"
,zhangsan,lisi,2000,transfer);
TransferThread lisiToZhangsan = new TransferThread("lisiToZhangsan"
,lisi,zhangsan,4000,transfer);
zhangsanToLisi.start();
lisiToZhangsan.start();
}
}
本例使用了接口,多态。父类引用指向子类对象
输出
zhangsanToLisi get zhangsan
lisiToZhangsan get lisi
这是一个相对比较典型的动态锁,虽然在TrasnferAccount.transfer方法中规定了加锁的顺序,但这个加锁不能确保调用输入from、to两个参数的顺序,导致形成死锁。
如果执行如下代码便不会出现死锁
TransferThread zhangsan1ToLisi1 = new TransferThread("zhangsan1ToLisi1"
,zhangsan1,lisi1,2000,transfer);
TransferThread zhangsan1ToLisi2 = new TransferThread("zhangsan1ToLisi2"
,zhangsan1,lisi2,2000,transfer);
zhangsan1ToLisi1.start();
zhangsan1ToLisi2.start();
三 死锁的解决方法
1.尝试取锁tryLock()
tryLock()方法是有返回值的,返回值是Boolean类型。它表示的是用来尝试获取锁:成功获取则返回true;获取失败则返回false
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
保证每个线程先顺利取到from,在取到to即可,如果没有就不断尝试去抢夺。
举例:
th1(F1,T1,2000)
th2(T1,F1,1000)
th1先抢到F1→还没抢到T1,th2就抢到了T1→th1无法进行,释放F1→th2抢到F1,执行命令→th2释放F1 释放T1(这是一种可能的情况)
public class SafeOperateToo implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount) throws InterruptedException {
Random r = new Random();
while(true) {//不断进行
if(from.getLock().tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " get "+from.getName());
if(to.getLock().tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " get "+to.getName());
//两把锁都拿到了
from.flyMoney(amount);
to.addMoney(amount);
break;
}finally {
to.getLock().unlock();//最总要释放锁to
}
}
}finally {
from.getLock().unlock();//无论有没有执行锁to代码,也要释放锁from
}
}
Thread.sleep(r.nextInt(10)); //线程睡眠小技巧,加快取锁
}
}
}