在java中多线程显得十分重要,在现实中,我们大家买车票的时候,肯定会接触到多线程,这篇博客将就多线程中的死锁重点讲解一下
要理解多线程,就必须理解线程。而要理解线程,就必须知道进程。
1、 进程
是一个正在执行的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
2、线程
就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。
一个进程中至少有一个线程。
3、多线程
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像种在一个进程中有多个线程执行的方式,就叫做多线程。
Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。假设有两个线程,分别代表两个饥饿的人,他们必须共享刀叉并轮流吃饭。他们都需要获得两个锁:共享刀和共享叉的锁。
假如线程 “A”获得了刀,而线程“B”获得了叉。线程“A”就会进入阻塞状态来等待获得叉,而线程“B”则阻塞来等待“A”所拥有的刀。这只是人为设计的例子,但尽管在运行时很难探测到,这类情况却时常发生。虽然要探测或推敲各种情况是非常困难的,但只要按照下面几条规则去设计系统,就能够避免Java线程死锁问题:
让所有的线程按照同样的顺序获得一组锁。这种方法消除了 X 和 Y 的拥有者分别等待对方的资源的问题。
将多个锁组成一组并放到同一个锁下。前面Java线程死锁的例子中,可以创建一个银器对象的锁。于是在获得刀或叉之前都必须获得这个银器的锁。
将那些不会阻塞的可获得资源用变量标志出来。当某个线程获得银器对象的锁时,就可以通过检查变量来判断是否整个银器集合中的对象锁都可获得。如果是,它就可以获得相关的锁,否则,就要释放掉银器这个锁并稍后再尝试。
同步中嵌套同步就有可能发生死锁:
死锁案例:
<span style="font-size:18px;">
public class Bolg9 {
/**
* @param args
*/
/*
* 当类的对象flag=1时(T1),先锁定O1,睡眠500毫秒,然后锁定O2;
* 而T1在睡眠的时候另一个flag=0的对象(T2)线程启动,先锁定O2,睡眠500毫秒,等待T1释放O1;
* T1睡眠结束后需要锁定O2才能继续执行,而此时O2已被T2锁定; T2睡眠结束后需要锁定O1才能继续执行,而此时O1已被T1锁定;
* T1、T2相互等待,都需要对方锁定的资源才能继续执行,从而死锁。
*/
public static void main(String[] args) {
DeadThread td1 = new DeadThread();
DeadThread td2 = new DeadThread();
td1.flag = 1;
td2.flag = 0;
new Thread(td1).start();
new Thread(td2).start();
}
}
class DeadThread implements Runnable {
public int flag = 1;
static Object o1 = new Object(), o2 = new Object();
@Override
public void run() {
System.out.println("flag=" + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
}</span>
发生死锁:
flag=1
flag=0
解决方式:
1)尽量使用tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
2)尽量使用java.util.concurrent(jdk 1.5以上)包的并发类代替手写控制并发,比较常用的是ConcurrentHashMap、ConcurrentLinkedQueue、AtomicBoolean等等,实际应用中java.util.concurrent.atomic十分有用,简单方便且效率比使用Lock更高
3)尽量降低锁的使用粒度,尽量不要几个功能用同一把锁
4)尽量减少同步的代码块