死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,
若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
下面通过代码演示:
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
根据输出结果可看出,两个线程分别拿了一个锁,然后分别进入等待另一个锁;但由于自各的另一个锁分别被另外一个线程持有且一直没有释放,所以都处于等待状态,形成了死锁。
在JVM的部署环境中输入指令:jps
查到程序运行的线程,接着输入指令:
jstack -l 23012
可查看当前死锁情况:
要想解决死锁问题,那就需要知道产生死锁的原因。资源一定是多于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
保证了两个线程的获取锁的顺序一致,即可解决死锁的问题。
下面先使用一个银行转账案例模拟动态死锁:
/**
*类说明:银行转账动作接口
*/
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
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一个随机的秒数,虽然让线程执行速度下降,但却极大地提高了所有线程总的取锁效率。