本文共 5598 字,大约阅读时间需要 18 分钟。
需要注意的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。但如果程序员错误地在两个线程中使用了递归锁,则很容易导致“死锁”出现:两个线程同时对同一个锁进行加锁,同时发现该锁已经锁定,彼此等待对方解锁,导致两个线程都无法执行下去。读线程从共享资源中读数据,同时写线程需要向共享资源中写数据,当多个读、写线程共用一个资源(缓冲区)的时候,据需要使用诸如criticalsection或者互斥量的排他性锁(独占锁)来控制资源的访问,但是针对读操作较为频繁,写操作相对较少的情况下,使用独占锁是很不划算的,需要耗费更多的时间和其他资源。
1.一个最简单的死锁案例
当一个线程永远地持有一个锁,并且其他线程都尝试获得这个锁时,那么它们将永远被阻塞。程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么这两个线程将永远地等待下去。这种就是最简答的死锁形式(或者叫做"抱死")。
2.锁顺序死锁
nsrecursivelock :递归锁,有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递归锁来解决。当多个线程访问mythread的run方法时,以排队的方式进行处理(这里排队是按照cpu分配的先后顺序而定的),一个线程想要执行synchronized修饰的方法里的代码,首先是尝试获得锁,如果拿到锁,执行synchronized代码块中的内容:拿不到锁,这个线程就会不断尝试获得这把锁,知道拿到位置,而且是多个线程同时去竞争这把锁。只要我们把“上锁”的操作始终由同一个线程来做即可避免“死锁”问题,但这样的话,并发请求的任务只能放在队列中由该线程依次执行(因为是后台执行,无需即时响应用户,所以可以这么做)。
如果一个线程对这种类型的互斥锁重复上锁,不会引起死锁,一个线程对这类互斥锁的多次重复上锁必须由这个线程来重复相同数量的解锁,这样才能解开这个互斥锁,别的线程才能得到这个互斥锁。死锁主要发生在有多个依赖锁存在时,会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生.如何避免死锁是使用互斥量应该格。上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。
2.1.动态的锁顺序死锁
大家都知道余额宝,"余额宝"是由国内第三方支付平台支付宝打造的一项全新的余额增值服务,用户把钱转入余额宝中,可以获得一定的基金收益,同时余额宝内的资金还能随时用于网上购物、支付宝转账等支付功能。各个银行的转账方法插卡—输入卡密码—选择转账交易—输入卡密码—输入转入卡卡号—输入转账金额—显示转入卡号、转入户名、转入金额并要求确认—交易完成显示交易金额和手续费并提示是否显示余额—询问是否打印交易凭条。而消费类支付账户的余额仅 可用于消费以及转账至客户本人同名银行账户(包括借记和贷记账户),不可用于转账至其他账户死锁怎么解决,也不可用于购买投资理财产品或服务。
所以写出的代码如下:
//动态的锁的顺序死锁
public class DynamicOrderDeadlock {
public static void transferMoney(Account fromAccount,Account toAccount,int amount,int from_index,int to_index) throws Exception {
System.out.println("账户 "+ from_index+"~和账户~"+to_index+" ~请求锁");
synchronized (fromAccount) {
System.out.println("账户 >>>"+from_index+" <<
synchronized (toAccount) {
System.out.println(" 账户 "+from_index+" & "+to_index+"都获得锁");
if (fromAccount.compareTo(amount) < 0) {
throw new Exception();
}else {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
}
static class Account {
private int balance = 100000;//这里假设每个人账户里面初始化的钱
private final int accNo;
private static final AtomicInteger sequence = new AtomicInteger();
public Account() {
accNo = sequence.incrementAndGet();
}
void debit(int m) throws InterruptedException {
Thread.sleep(5);//模拟操作时间
balance = balance + m;
}
void credit(int m) throws InterruptedException {
Thread.sleep(5);//模拟操作时间
balance = balance - m;
}
int getBalance() {
return balance;
}
int getAccNo() {
return accNo;
}
public int compareTo(int money) {
if (balance > money) {
return 1;
}else if (balance < money) {
return -1;
}else {
return 0;
}
}
}
}
public class DemonstrateDeadLock {
private static final int NUM_THREADS = 5;
private static final int NUM_ACCOUNTS = 5;
private static final int NUM_ITERATIONS = 100000;
public static void main(String[] args) {
final Random rnd = new Random();
final Account[] accounts = new Account[NUM_ACCOUNTS];
for(int i = 0;i < accounts.length;i++) {
accounts[i] = new Account();
}
class TransferThread extends Thread{
@Override
public void run() {
for(int i = 0;i < NUM_ITERATIONS;i++) {
int fromAcct = rnd.nextInt(NUM_ACCOUNTS);
int toAcct =rnd.nextInt(NUM_ACCOUNTS);
int amount = rnd.nextInt(100);
try {
DynamicOrderDeadlock.transferMoney(accounts[fromAcct],accounts[toAcct], amount,fromAcct,toAcct);
//InduceLockOrder.transferMoney(accounts[fromAcct],accounts[toAcct], amount);
//InduceLockOrder2.transferMoney(accounts[fromAcct],accounts[toAcct], amount);
}catch (Exception e) {
System.out.println("发生异常-------"+e);
}
}
}
}
for(int i = 0;i < NUM_THREADS;i++) {
new TransferThread().start();
}
}
}
打印结果如下:
注意:这里的结果是我把已经执行完的给删除后,只剩下导致死锁的请求.
从打印结果的图片中可以的得到结论:由于我们无法控制transferMoney中的参数的顺序,而这些参数顺序取决于外部的输入。所以两个线程同时调用transferMoney,一个线程从X向Y转账,另一个线程从Y向X转账,那么就会发生互相等待锁的情况,导致死锁。
解决问题方案:定义锁的顺序,并且整个应用中都按照这个顺序来获取锁。
方案一
2 el表达式可操作常量 变量 和隐式对象. 最常用的 隐式对象有${param}和${paramvalues}.${param}表示返回请求参数中单个字符串的值.${paramvalues}表示返回请求参数的一组值.pagescope表示页面范围的变量.requestscope表示请求对象的变量.sessionscope表示会话范围内的变量.applicationscope表示应用范围的变量.。2 el表达式可操作常量 变量 和隐式对象. 最常用的 隐式对象有${param}和${paramvalues}. ${param}表示返回请求参数中单个字符串的值. ${paramvalues}表示返回请求参数的一组值.pagescope表示页面范围的变量.requestscope表示请求对象的变量. sessionscope表示会话范围内的变量.applicationscope表示应用范围的变量.。${foo:=bar}将会设置变量$foo.这个字符串运算符会检测foo存在并且不为空值.如果他不为空,则会返回他的值,但是如果是相反的情况,就会将foo的值设为bar并且会返回替换的结果值.。
具体代码如下:
//通过锁顺序来避免死锁
public class InduceLockOrder {
private static final Object tieLock = new Object();
public static void transferMoney(final Account fromAcct, final Account toAcct, final int amount)
throws Exception {
class Helper {
public void transfer() throws Exception {
if (fromAcct.compareTo(amount) < 0) {
throw new Exception();
} else {
fromAcct.debit(amount);
toAcct.credit(amount);
}
}
}
int fromHash = System.identityHashCode(fromAcct);
int toHash = System.identityHashCode(toAcct);
if (fromHash < toHash) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
} else if (fromHash > toHash) {
synchronized (toAcct) {
synchronized (fromAcct) {
new Helper().transfer();
}
}
} else {
synchronized (tieLock) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
}
}
}
static class Account {
private int balance = 100000;
public Account() {
}
void debit(int m) throws InterruptedException {
Thread.sleep(5);
balance = balance + m;
}
void credit(int m) throws InterruptedException {
Thread.sleep(5);
balance = balance - m;
}
int getBalance() {
return balance;
}
public int compareTo(int money) {
if (balance > money) {
return 1;
}else if (balance < money) {
return -1;
}else {
return 0;
}
}
}
}
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-113609-1.html