在我们开发中如果我们使用多线程并发执行并不能带来很大的效率提升,我建议尽可能的少使用一些多线程,因为我们稍有不注意就可能带来意想不到的结果。下面简单看一下出现死锁情况我们该怎么处理呢?
1。比如有一个死锁的线程,我们此时可以通过一些命令查看栈情况,查找我们的执行进程情况,比如:jps -m,详细jps命令可以参考https://www.jianshu.com/p/d39b2e208e72 简书中一篇文章。
2.查找到我们问题进程id,使用jstack 命令进行分析。
那么我们遇到死锁这种情况怎么解决呢,我个人总结一下处理方式,如有不妥之处请广大友人提出,共同进步。
1.保证线程执行顺序
典型的死锁现象:比如a,b两个线程,在处理任务时我们使用了嵌套,如果顺序不当很容易出现死锁现象,我们以转账为例:以下是关键代码:
public class NormalTransfer implements ITransfer{
@Override
public void transfer(Account from, Account 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);
}
}
}
}
由上述转账方法我们知道,在我们转账时需要锁定双方进行转账处理,如果我们传入账号顺序不同那么很容易产生死锁,在此时我们尽可能的保证线程的执行顺序,一个解决方案是我们可以使用一种策略就是设法让现成按我们预想结果处理,我们使用hashcode值进行业务处理逻辑的执行,我们也需要注意一个问题,如果使用hashcode,如果被别人复写了怎么办,那么我选择了system的hashcode的一种产生方式,源码可以得知这种方式不能进行复写,在一个我们呢也要考虑在我们每天银行转账那么多,很有可能hashcode值一样,此时我们需要添加一个竞争锁来处理这种情况,具体代码如下:
public class SafeTransfer implements ITransfer {
private static Object tieLock = new Object();
@Override
public void transfer(Account from, Account to, int amount)
throws InterruptedException {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
if(fromHash<toHash){
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);
System.out.println(from);
System.out.println(to);
}
}
}else if(toHash<fromHash){
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);
System.out.println(from);
System.out.println(to);
}
}
}else{
synchronized (tieLock){
synchronized (to){
System.out.println(Thread.currentThread().getName()+" get "+from.getName());
Thread.sleep(100);
synchronized (from){
System.out.println(Thread.currentThread().getName()
+" get "+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
}
}
2.我们可以使用jdk提供的信号量进行控制
信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。
对于无法成功获取的情况,一般就是重复尝试,或指定尝试的次数,也可以马上退出。
来看下如下代码:
Java代码
- package lockTest;
- import java.util.Date;
- import java.util.concurrent.Semaphore;
- import java.util.concurrent.TimeUnit;
- public class UnLockTest {
- public static String obj1 = "obj1";
- public static final Semaphore a1 = new Semaphore(1);
- public static String obj2 = "obj2";
- public static final Semaphore a2 = new Semaphore(1);
- public static void main(String[] args) {
- LockAa la = new LockAa();
- new Thread(la).start();
- LockBb lb = new LockBb();
- new Thread(lb).start();
- }
- }
- class LockAa implements Runnable {
- public void run() {
- try {
- System.out.println(new Date().toString() + " LockA 开始执行");
- while (true) {
- if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
- System.out.println(new Date().toString() + " LockA 锁住 obj1");
- if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
- System.out.println(new Date().toString() + " LockA 锁住 obj2");
- Thread.sleep(60 * 1000); // do something
- }else{
- System.out.println(new Date().toString() + "LockA 锁 obj2 失败");
- }
- }else{
- System.out.println(new Date().toString() + "LockA 锁 obj1 失败");
- }
- UnLockTest.a1.release(); // 释放
- UnLockTest.a2.release();
- Thread.sleep(1000); // 马上进行尝试,现实情况下do something是不确定的
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- class LockBb implements Runnable {
- public void run() {
- try {
- System.out.println(new Date().toString() + " LockB 开始执行");
- while (true) {
- if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
- System.out.println(new Date().toString() + " LockB 锁住 obj2");
- if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
- System.out.println(new Date().toString() + " LockB 锁住 obj1");
- Thread.sleep(60 * 1000); // do something
- }else{
- System.out.println(new Date().toString() + "LockB 锁 obj1 失败");
- }
- }else{
- System.out.println(new Date().toString() + "LockB 锁 obj2 失败");
- }
- UnLockTest.a1.release(); // 释放
- UnLockTest.a2.release();
- Thread.sleep(10 * 1000); // 这里只是为了演示,所以tryAcquire只用1秒,而且B要给A让出能执行的时间,否则两个永远是死锁
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
public class UnLockTest { public static String obj1 = "obj1"; public static final Semaphore a1 = new Semaphore(1); public static String obj2 = "obj2"; public static final Semaphore a2 = new Semaphore(1); public static void main(String[] args) { LockAa la = new LockAa(); new Thread(la).start(); LockBb lb = new LockBb(); new Thread(lb).start(); } } class LockAa implements Runnable { public void run() { try { System.out.println(new Date().toString() + " LockA 开始执行"); while (true) { if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockA 锁住 obj1"); if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockA 锁住 obj2"); Thread.sleep(60 * 1000); // do something }else{ System.out.println(new Date().toString() + "LockA 锁 obj2 失败"); } }else{ System.out.println(new Date().toString() + "LockA 锁 obj1 失败"); } UnLockTest.a1.release(); // 释放 UnLockTest.a2.release(); Thread.sleep(1000); // 马上进行尝试,现实情况下do something是不确定的 } } catch (Exception e) { e.printStackTrace(); } } } class LockBb implements Runnable { public void run() { try { System.out.println(new Date().toString() + " LockB 开始执行"); while (true) { if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockB 锁住 obj2"); if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockB 锁住 obj1"); Thread.sleep(60 * 1000); // do something }else{ System.out.println(new Date().toString() + "LockB 锁 obj1 失败"); } }else{ System.out.println(new Date().toString() + "LockB 锁 obj2 失败"); } UnLockTest.a1.release(); // 释放 UnLockTest.a2.release(); Thread.sleep(10 * 1000); // 这里只是为了演示,所以tryAcquire只用1秒,而且B要给A让出能执行的时间,否则两个永远是死锁 } } catch (Exception e) { e.printStackTrace(); } } }
看打印情况:
Java代码
- Mon Mar 31 10:57:07 CST 2014 LockA 开始执行
- Mon Mar 31 10:57:07 CST 2014 LockB 开始执行
- Mon Mar 31 10:57:07 CST 2014 LockB 锁住 obj2
- Mon Mar 31 10:57:07 CST 2014 LockA 锁住 obj1
- Mon Mar 31 10:57:08 CST 2014LockB 锁 obj1 失败
- Mon Mar 31 10:57:08 CST 2014LockA 锁 obj2 失败
- Mon Mar 31 10:57:09 CST 2014 LockA 锁住 obj1
- Mon Mar 31 10:57:09 CST 2014 LockA 锁住 obj2
Mon Mar 31 10:57:07 CST 2014 LockA 开始执行 Mon Mar 31 10:57:07 CST 2014 LockB 开始执行 Mon Mar 31 10:57:07 CST 2014 LockB 锁住 obj2 Mon Mar 31 10:57:07 CST 2014 LockA 锁住 obj1 Mon Mar 31 10:57:08 CST 2014LockB 锁 obj1 失败 Mon Mar 31 10:57:08 CST 2014LockA 锁 obj2 失败 Mon Mar 31 10:57:09 CST 2014 LockA 锁住 obj1 Mon Mar 31 10:57:09 CST 2014 LockA 锁住 obj2
第一次两个线程获取信号量时都会失败,因为失败后B等待时间长,所以A再次尝试时会成功。
实际中,你执行任务内容不同,所需时间是不同的。另外不同的线程,对于获取信号量失败的处理也可能是不同的。所以,虽然不会产生死锁,但是你要根据实际情况,来编写获取失败后的处理机制。
3.我们可以使用重入锁进行处理,在我们账户对象中设置重入锁属性,在我们线程执行时首先我们获取我们重入锁,然后才能进行执行任务。
public class TryLockTransfer implements ITransfer {
@Override
public void transfer(Account from, Account to, int amount)
throws InterruptedException {
Random r = new Random();
while(true){
if(from.getLock().tryLock()){
try{
System.out.println(Thread.currentThread().getName()
+" get from "+from.getName());
if(to.getLock().tryLock()){
try{
System.out.println(Thread.currentThread().getName()
+" get to "+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
break;
}finally {
to.getLock().unlock();
}
}
}finally {
from.getLock().unlock();
}
}
Thread.sleep(r.nextInt(5));//防止产生活锁
}
}
}
注意:上述产生活锁产生解决是增加休眠时间,活锁个人理解就是我们多线程争夺琐时,比如a线程想要获取锁,但是又考虑到别人也需要,所以此时它退出竞争,但是又一直试图去获取,这样就形成了活锁。