Java死锁及如何解决死锁

死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,

若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

1、普通死锁

1.1 死锁代码

下面通过代码演示:

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();
        }
    }
}

输出结果:

TestDeadLock get second
SubTestThread get first

根据输出结果可看出,两个线程分别拿了一个锁,然后分别进入等待另一个锁;但由于自各的另一个锁分别被另外一个线程持有且一直没有释放,所以都处于等待状态,形成了死锁。

 

1.2 查看死锁

在JVM的部署环境中输入指令:jps

Java死锁及如何解决死锁_第1张图片

查到程序运行的线程,接着输入指令:

jstack -l 23012

可查看当前死锁情况:

Java死锁及如何解决死锁_第2张图片

2、解决死锁

要想解决死锁问题,那就需要知道产生死锁的原因。资源一定是多于1个,同时小于等于竞争的线程数,资源只有一个,只会产生激烈的竞争。

死锁的根本成因:获取锁的顺序不一致导致

2.1 解决简单顺序锁

解决思想是保证每个线程获取锁的顺序是前后致的,如还是到上述第一节的例子,保证两个线程都是先获取fisrtLock,再获取secondLock;获取顺序一致后,就能解决死锁的问题

 

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 (valueFirst) { //修改为先拿第一个锁
            System.out.println(threadName+" get first");
            Thread.sleep(101);
            synchronized (valueSecond) { //再拿第二个锁
                System.out.println(threadName+" get second");
            }
        }
    }
    //执行先拿第二个锁,再拿第一个锁
    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();
        }
    }
}

输出结果:

TestDeadLock get first
TestDeadLock get second
SubTestThread get first
SubTestThread get second

保证了两个线程的获取锁的顺序一致,即可解决死锁的问题。

2.2 使用System.identityHashCode(obj)解决动态死锁

下面先使用一个银行转账案例模拟动态死锁:

 

/**
 *类说明:银行转账动作接口
 */
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;
    }
}
/**
 *类说明:不安全的转账动作的实现
 */
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 SafeOperateToo();
        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两个参数的顺序,导致形成死锁。

因此,为确保不形成动态锁,同时避免传入的对象重写了hashCode方法,可以使用JDK系统自带的System.identityHashCode()方法,降低哈希冲突

 

public class SafeLock implements ITransfer {
	private static Object tieLock = new Object();//哈希冲突时使用的锁
    @Override
    public void transfer(UserAccount from, UserAccount to, int amount)
            throws InterruptedException {
    	int fromHash = System.identityHashCode(from);
    	int toHash = System.identityHashCode(to);
    	//先锁hash小的那个
    	if(fromHash

2.3 使用尝试取锁tryLock()

 

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();
    					}
    				}
    			}finally {
    				from.getLock().unlock();
    			}
    		}
    		Thread.sleep(r.nextInt(10)); //线程睡眠小技巧,加快取锁
    	}
    }
}

使用tryLock()的方法,如果取不到锁就释放当前锁,保证别的线程能取到。

注意:这里有一个小技巧,就是让取不到锁的线程睡眠sleep一个随机的秒数,虽然让线程执行速度下降,但却极大地提高了所有线程总的取锁效率。

 

 

你可能感兴趣的:(Java死锁及如何解决死锁)