0
点赞
收藏
分享

微信扫一扫

第14章 多线程(3) 线程并发问题与可重入锁


前言

这是线程真正开始变得有意思的部分,各位学习前面的概念头昏脑胀了没?是时候来玩一玩了。线程同步是什么呢?

相当于一个很有意思的游戏,玩的好的随手编辑几句,性能杠杠的,众人惊为天人,纷纷膜拜(心中OS:哈哈哈,哥就是有实力!这么简单,随便玩一玩就OK);

玩不好的呢,看到就头疼:啊啥玩意,又死锁了 T T,为啥会死锁,我不知道啊,看我无辜的小眼神(●'◡'●)(怂.png,要不,我们重启?)

大神还是FIVE就看这部分学的好不好了,你准备好了么?

第14章 多线程(3) 线程并发问题与可重入锁_i++

14.5 同步

线程的特点之一是内存共享。所以就会造成一个问题:同时修改怎么办?谁先谁后,这就是竞争条件

14.5.1 竞争条件的一个例子

一个银行有若干账户。随机生成多笔账户间相互转账的交易,每笔交易中,都会从一个账户随机转移一定的金额到另一个账户。

A->B的转账100块操作:从A用户扣除100,B用户增加100

Bank.java

功能:两个账户从来源账户转指定金额到目标账户,并验证账户金额

🍹 建立指定数目的账户

🍹 完成从一个账户转账到另一个账户

🍹 计算所有账户总金额

🍹 获取指定的账户数目

❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤

public class Bank {
private final double[] accounts;
public Bank(int n, double initialBalance){
accounts = new double[n];
for(int i = 0; i < n; i++){
accounts[i] = initialBalance;
}


}

public void transfer(int from, int to, double amount){
if(accounts[from] < amount) return;
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
}

private double getTotalBalance(){
double sum = 0;
for(double a: accounts){
sum += a;
}

return sum;
}

public int size(){
return accounts.length;
}
}

❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤

TransferRunnable.java

功能:当前账户到随机账户转账

🍹 确定来源账户和最大转账金额

🍹 完成一次从来源账户到随机账户的转账

🍹 睡眠随机时间,继续从来源账户到重新随机的账户转账

❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤

public class TransferRunnable implements Runnable {
private Bank bank;
private int fromAccount;
private double maxAmount;
private int DELAY = 10;
public TransferRunnable(Bank b, int from, double max){
bank = b;
fromAccount = from;
maxAmount = max;
}

public void run(){
try{
while(true){
int toAccount = (int) (bank.size()*Math.random());
double amount = maxAmount * Math.random();
bank.transfer(fromAccount, toAccount,amount);
Thread.sleep((int)(DELAY * Math.random()));
}
}catch(InterruptedException e){}
}
}

❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤

UnsynchBankTest.java

功能:当前账户到随机账户转账

🍹 建立一个银行和100个账户

🍹 开100个线程代表不同的来源账户

🍹 不同的账户进行转账

🍹 10秒后关闭所有线程(因为除了主线程所有线程都开了守护,只要主线程结束都可以结束)

❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤

public class UnsynchBankTest {
public static final int NACCOUNTS = 100;
public static final double INITIAL_BALANCE = 1000;

public static void main(String[] args) throws InterruptedException {
Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE);
int i;
for(i = 0; i < NACCOUNTS; i++){
TransferRunnable r = new TransferRunnable(b,i,INITIAL_BALANCE);
Thread t = new Thread(r);
t.setDaemon(true);
t.start();
}

Thread.sleep(10000);
}
}

❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤

运行结果:

部分计算正确,部分计算错误,中间有一些计算错误的总金额

🚗🚓🚕🛺🚙🚌🚐🚎🚑🚒🚚🚛🚜🚘🚔🚖🚍🦽🦼🛹🚲🛴🛵🏍

第14章 多线程(3) 线程并发问题与可重入锁_java_02

🚗🚓🚕🛺🚙🚌🚐🚎🚑🚒🚚🚛🚜🚘🚔🚖🚍🦽🦼🛹🚲🛴🛵🏍

14.5.2 详解竞争条件

为什么金额有时候是错误的呢?

1)accounts[to] += amount; 不是原子操作(对应于 JVM 指令不止一条),实际上这个加法操作对于计算机 JVM 指令来说,可没有那么简单,分为三步:

🍹 accounts[to] 放到寄存器

🍹 增加 amount

🍹 取出赋值给 accounts[to]

那就可能存在,某个线程执行了前面的 1 到 2 步,另一个线程开始执行,完成了三步,那么再回到第一个线程进行赋值的时候,金额就不正确了

2)第一个线程执行完减法操作,被剥夺运行权,第二个线程执行完减法操作,总金额变少,进行输出,因为前面加法的操作没有执行完,导致总金额减少

这个问题有多严重

1)银行的钱可能变多,用户的钱变少,用户骂银行扣钱,放弃使用银行,导致银行倒闭

2)银行的钱变少,用户的钱变多,因为银行的钱变少,钱总是不够用,银行倒闭

怎么解决线程竞争问题?

保证在无法控制的部分,强制此部分代码必须执行完成,才能切换下一个线程,这就需要用到线程的究极大招:锁

14.5.3 锁对象

为了保证线程无法控制的部分,能够顺利独立、连续的完成,就要用到锁。ReentrantLock 是一种常见的锁,它的用法特别简单:

锁定:myLock.lock();

解锁:myLock.unlock();

在我们的银行加个锁:

❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤

public class Bank {
private final double[] accounts;
private Lock bankLock = new ReentrantLock();
public Bank(int n, double initialBalance){
accounts = new double[n];
for(int i = 0; i < n; i++){
accounts[i] = initialBalance;
}


}

public void transfer(int from, int to, double amount){
bankLock.lock();
if(accounts[from] < amount) return;
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
bankLock.unlock();
}

private double getTotalBalance(){
double sum = 0;
for(double a: accounts){
sum += a;
}

return sum;
}

public int size(){
return accounts.length;
}
}

❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤❤🧡💛💚💙💜🤎🖤

运行结果:

🍹 跑的速度变慢了

🍹 跑的结果一直正确

🚗🚓🚕🛺🚙🚌🚐🚎🚑🚒🚚🚛🚜🚘🚔🚖🚍🦽🦼🛹🚲🛴🛵🏍

第14章 多线程(3) 线程并发问题与可重入锁_idea_03

🚗🚓🚕🛺🚙🚌🚐🚎🚑🚒🚚🚛🚜🚘🚔🚖🚍🦽🦼🛹🚲🛴🛵🏍

这段lock()/unlock()之间的代码和公厕是一样的。一个人上大号进去以后把门锁了,后续的人要进同一个厕所就需要等待前面的人出来才能进厕所。

可重入锁:一个线程可以重复获得多次锁。相当于一个门装了10个锁,那就要10把不同的钥匙把这些锁都打开,才可以解锁。

第14章 多线程(3) 线程并发问题与可重入锁_赋值_04

所以刚刚这段代码锁两次解两次也是可以的

后记

其实大概看了下,感觉这部分无论是概念方面还是生命周期方面,还是例子都不是特别全,但是没看完不确定缺了哪些(其实基础篇好像很少有特别全的,编程思想的比较全,但是难懂)。等看完了,我再看一遍线程的知识点给大家补充,不要着急。

相关内容:选择 《Java核心技术 卷1》查找相关笔记

评论🌹点赞👍收藏✨关注👀,是送给作者最好的礼物,愿我们共同学习,一起进步

公众号 钰娘娘知识汇总 

举报

相关推荐

0 条评论