Java死锁及如何解决死锁

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

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

jstack -l 23012

可查看当前死锁情况:

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

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

}

}

}else if(toHash

synchronized (to){

System.out.println(Thread.currentThread().getName()

+" get"+to.getName());

Thread.sleep(100);

synchronized (from){

System.out.println(Thread.currentThread().getName()

+" get"+from.getName());

from.flyMoney(amount);

to.addMoney(amount);

}

}

}else {//解决hash冲突的方法

synchronized (tieLock) {

synchronized (from) {

synchronized (to) {

from.flyMoney(amount);

to.addMoney(amount);

}

}

}

}

}

}

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一个随机的秒数,虽然让线程执行速度下降,但却极大地提高了所有线程总的取锁效率。

相关推荐

巴拿马世界杯首粒进球现场(燃爆球场的梦想之翼,巴拿马迎来历史性时刻)
汽车之家
365bet英超

汽车之家

📅 08-20 👁️ 5652
荐片速度突然变慢
365bet英超

荐片速度突然变慢

📅 08-03 👁️ 8379