什么是死鎖
線程死鎖是指由于兩個(gè)或者多個(gè)線程互相持有對(duì)方所需要的資源偎箫,導(dǎo)致這些線程處于等待狀態(tài)栋操,無法前往執(zhí)行郑藏。當(dāng)線程進(jìn)入對(duì)象的 synchronized 代碼塊時(shí)鲤孵,便占有了資源,直到它退出該代碼塊或者調(diào)用wait方法,才釋放資源赞咙,在此期間责循,其他線程將不能進(jìn)入該代碼塊。當(dāng)線程互相持有對(duì)方所需要的資源時(shí)攀操,會(huì)互相等待對(duì)方釋放資源院仿,如果線程都不主動(dòng)釋放所占有的資源,將產(chǎn)生死鎖速和。
死鎖的產(chǎn)生是必須要滿足一些特定條件的:
1.互斥條件:進(jìn)程對(duì)于所分配到的資源具有排它性歹垫,即一個(gè)資源只能被一個(gè)進(jìn)程占用,直到被該進(jìn)程釋放
2.請(qǐng)求和保持條件:一個(gè)進(jìn)程因請(qǐng)求被占用資源而發(fā)生阻塞時(shí)颠放,對(duì)已獲得的資源保持不放排惨。
3.不剝奪條件:任何一個(gè)資源在沒被該進(jìn)程釋放之前,任何其他進(jìn)程都無法對(duì)他剝奪占用
4.循環(huán)等待條件:當(dāng)發(fā)生死鎖時(shí)慈迈,所等待的進(jìn)程必定會(huì)形成一個(gè)環(huán)路(類似于死循環(huán))若贮,造成永久阻塞。
一個(gè)死鎖的例子:
public class NormalDeadLock {
private static Object valueFirst = new Object();
private static Object valueSecond = new Object();
private static void firstToSecond() 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 secondToFirst() throws InterruptedException{
String threadName = Thread.currentThread().getName();
synchronized (valueSecond){
System.out.println(threadName + " get second");
Thread.sleep(100);
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{
secondToFirst();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args){
Thread.currentThread().setName("TestDeadLock");
TestThread testThread = new TestThread("SubTestThread");
testThread.start();
try{
firstToSecond();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
這個(gè)程序運(yùn)行結(jié)果:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=57881:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/out/production/javastudy:/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/lib/kotlin-stdlib.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/lib/kotlin-reflect.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/lib/kotlin-test.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/lib/kotlin-stdlib-jdk7.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/lib/kotlin-stdlib-jdk8.jar:/Users/theodore/.m2/repository/org/thymeleaf/thymeleaf/3.0.11.RELEASE/thymeleaf-3.0.11.RELEASE.jar:/Users/theodore/.m2/repository/ognl/ognl/3.1.12/ognl-3.1.12.jar:/Users/theodore/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/Users/theodore/.m2/repository/org/attoparser/attoparser/2.0.5.RELEASE/attoparser-2.0.5.RELEASE.jar:/Users/theodore/.m2/repository/org/unbescape/unbescape/1.1.6.RELEASE/unbescape-1.1.6.RELEASE.jar:/Users/theodore/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxb-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/gmbal-api-only.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/javax.annotation.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/ha-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxws-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jsr181-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/FastInoset.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/mimepull.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/mail.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxws-rt.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxb-impl.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxb-xjc.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/policy.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/management-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxws-tools.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/saaj-impl.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/stax-ex.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/stax2-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/streambuffer.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/saaj-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/woodstox-core-asl.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/javax.persistence.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-aspects-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-instrument-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-expression-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-context-support-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-aop-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-instrument-tomcat-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-context-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-core-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-jms-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-jdbc-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-oxm-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-messaging-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-orm-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-tx-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/commons-logging-1.2.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-test-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/aopalliance-1.0.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-beans-4.3.18.RELEASE.jar" com.enjoy.MultiThread.ch7.NormalDeadLock
SubTestThread get second
TestDeadLock get first
程序是無法停止的痒留,因?yàn)?SubTestThread 先獲取了第二個(gè)鎖谴麦,然后去嘗試獲取第一個(gè)鎖。這個(gè)時(shí)候 TestDeadLock 先獲取了第一個(gè)鎖伸头,不釋放鎖匾效。SubTestThread 線程就一直等著。然后 TestDeadLock 在去獲取第二個(gè)鎖恤磷,這個(gè)時(shí)候第二個(gè)鎖被第一個(gè)線程占用不釋放面哼。
synchronized 不走出代碼塊是不會(huì)釋放鎖的。這就導(dǎo)致了死鎖扫步。代碼沒有保證獲得鎖的順序?qū)е铝藘蓚€(gè)線程互相等待魔策。造成死鎖。
再來看下面這個(gè)代碼:
//不安全的轉(zhuǎn)賬動(dòng)作
public class TransferAccount implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amout) throws InterruptedException {
synchronized (from){//先鎖轉(zhuǎn)出
System.out.println(Thread.currentThread().getName() + "get" + from.getName());
Thread.sleep(100);
synchronized (to){//后鎖轉(zhuǎn)入
System.out.println(Thread.currentThread().getName() + "get" + to.getName());
from.flyMoney(amout);
to.addMoney(amout);
}
}
}
}
這段代碼也會(huì)產(chǎn)生死鎖河胎,為什么呢闯袒?明明代碼保證了獲取鎖的順序呀?
對(duì)游岳,但是獲取鎖的順序是調(diào)用的時(shí)候決定的政敢,如果兩個(gè)線程,一個(gè)是 A 轉(zhuǎn)賬給 B胚迫,另一個(gè)是 B 轉(zhuǎn)賬給 A喷户。這個(gè)時(shí)候獲取鎖的順序就又不能保證了。就造成了思索访锻。
這兩段代碼第一個(gè)就是褪尝,靜態(tài)死鎖闹获,第二個(gè)就是,動(dòng)態(tài)死鎖恼五。那么怎么解決呢昌罩?有幾個(gè)解決方案:
先定義一個(gè)賬戶類 有賬戶名字和賬戶余額,還實(shí)現(xiàn)了轉(zhuǎn)入轉(zhuǎn)出方法灾馒。
//用戶賬戶實(shí)體類
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 money) {
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public int getMoney() {
return money;
}
@Override
public String toString() {
return "UserAccount{" +
"name='" + name + '\'' +
", money=" + money +
", lock=" + lock +
'}';
}
//轉(zhuǎn)入金額
public void addMoney(int amount){
money += amount;
}
//轉(zhuǎn)出金額
public void flyMoney(int amount){
money -= amount;
}
}
定義一個(gè)接口:
//銀行轉(zhuǎn)賬接口
public interface ITransfer {
void transfer(UserAccount from,UserAccount to,int amout) throws InterruptedException;
}
方案1:
//不會(huì)產(chǎn)生死鎖的安全轉(zhuǎn)賬 通過hashcode保證順序
public class SafeOperate implements ITransfer {
private static Object tieLock = new Object();//加時(shí)賽鎖
@Override
public void transfer(UserAccount from, UserAccount to, int amout) 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(amout);
to.addMoney(amout);
}
}
}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(amout);
to.addMoney(amout);
}
}
}else{//哈希沖突
synchronized (tieLock){
synchronized (from){
synchronized (to){
from.flyMoney(amout);
to.addMoney(amout);
}
}
}
}
}
}
通過兩個(gè)鎖的 hashcode 保證一定先鎖小的那個(gè),但是 hash 會(huì)有千萬分之一的沖突遣总,雖然不多睬罗,但是也需要考慮進(jìn)去。再定義一個(gè)鎖,在獲得 2 個(gè)對(duì)象的鎖之前,就要獲得這個(gè)鎖旭斥。保證順序容达。
方案2:
//用 trylock 保證不會(huì)產(chǎn)生鎖
public class SafeOperateToo implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amout) 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(amout);
to.addMoney(amout);
break;
}finally {
to.getLock().unlock();
}
}
}finally {
from.getLock().unlock();
}
}
SleepTools.ms(r.nextInt(10));//為了保證兩個(gè)鎖之間不會(huì)互相謙讓,導(dǎo)致誰都沒有獲得鎖
}
}
}
上面那個(gè)代碼看起來有些麻煩垂券,這個(gè)方案花盐,用 trylock 去嘗試獲取鎖,獲取不到線程也不會(huì)阻塞菇爪,在 while 循環(huán)中會(huì)再次去嘗試獲取鎖算芯,直到獲取到了為止。但是這個(gè)有個(gè)問題凳宙,如果兩個(gè)線程同時(shí)去嘗試獲取鎖熙揍,有可能兩個(gè)線程都獲取不到,然后他們就一直重復(fù)這個(gè)動(dòng)作氏涩。雖然沒有造成死鎖届囚,但是他們都沒有繼續(xù)下面的任務(wù),這個(gè)就是活鎖是尖。為了解決這個(gè)問題意系,我們可以對(duì)重試機(jī)制引入一些隨機(jī)性,不指定同時(shí)重發(fā)饺汹,而是重發(fā)的時(shí)間內(nèi)是隨機(jī)的蛔添。通過隨機(jī)的等待再發(fā)送能夠相當(dāng)有效的避免活鎖的發(fā)生。
在定義一個(gè)類來測(cè)試我們的程序:
//模擬公司轉(zhuǎn)賬
public class PayCompay {
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;
}
@Override
public void run() {
Thread.currentThread().setName(name);
try{
transfer.transfer(from,to,amount);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args){
PayCompay payCompay = new PayCompay();
UserAccount zhangsan = new UserAccount("zhangsan",20000);
UserAccount lisi = new UserAccount("lisi",20000);
ITransfer transfer = new SafeOperateToo();//new SafeOperate or new TransferAccount
TransferThread zhangsanToLisi = new TransferThread("zhangsanToLisi",zhangsan,lisi,2000,transfer);
TransferThread lisiToZhangsan = new TransferThread("lisiToZhangsan",lisi,zhangsan,4000,transfer);
zhangsanToLisi.start();
lisiToZhangsan.start();
}
}
我的代碼 github 地址: https://github.com/theodore816/javastudy/tree/master/com/enjoy/MultiThread/ch7
參考文獻(xiàn)
https://blog.csdn.net/amd123456789/article/details/80867948
https://blog.csdn.net/u011116672/article/details/51051352
https://blog.csdn.net/w1014074794/article/details/51114752
https://www.cnblogs.com/hadoop-dev/p/6899171.html
https://www.cnblogs.com/baizhanshi/p/5437933.html