java同步与死锁_Java多线程 - 线程同步与死锁

news/2024/7/1 2:53:58

一、线程同步

1)模拟多个用户同时从银行账户里面取钱

● Account 类:银行账户类,里面有一些账户的基本信息,以及操作账户信息的方法

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

//模拟银行账户

classAccount {private String accountNo;//账号

private double balance;//余额

publicAccount() {

}public Account(String accountNo, doublebalance) {super();this.accountNo =accountNo;this.balance =balance;

}publicString getAccountNo() {returnaccountNo;

}public voidsetAccountNo(String accountNo) {this.accountNo =accountNo;

}public doublegetBalance() {returnbalance;

}public void setBalance(doublebalance) {this.balance =balance;

}

}

Account.java

● DrawThread 类继承了Thread,是一个多线程类,用于模拟多个用户操作同一个账户的信息

class DrawThread extendsThread {private Account account;//银行账户

private double money;//操作金额

public DrawThread(String name, Account account, doublemoney) {super(name);this.account =account;this.money =money;

}//多个线程修改同一个共享数据,可能发生线程安全问题

@Overridepublic voidrun() {if (account.getBalance() >money) {

System.out.println("【" + getName() + "】取钱:" + " " +money);try{

Thread.sleep(500);

}catch(Exception e) {

e.printStackTrace();

}

account.setBalance(account.getBalance()-money);

System.out.println("\t账户余额为" + " " +account.getBalance());

}else{

System.out.println("【" + getName() + "】取钱:余额不足");}

}

}

● 测试类

public classTest {public static voidmain(String[] args) {//初始账户

Account account = new Account("88888888", 1000);//模拟两个线程同时操作账号

new DrawThread("甲", account, 800).start();new DrawThread("乙", account, 800).start();

}

}

我们现在希望实现的操作是模拟多个用户同时从银行账户里面取钱,如果用户取钱数小于等于当前账户余额,则提示取款成功,并将余额减去取款钱数,如果余额不足,则提示余额不足,取款失败。

- 运行程序可能会看到如下运行结果:

3663b91b483bc4d96b2072a4e9194833.png

45bdf45a7123ab0ae7fc42813b6a6309.png

9da7ad8c8264e375818f65e6f8f3fc07.png

645da478b58dbf89c95cee1f8fda4c88.png

同一账户同时操作,造成数据异常。因为线程调度的不确定性,所以出现了线程安全问题。

2)线程安全问题产生的原因

① 多个线程在操作共享数据

② 操作共享数据的线程代码有多条

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

3)解决思路

① 将多条操作的共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其它线程时不可以参与运算。

② 必须要当前线程把这些代码执行完毕后,其它线程才可以参与运算。

4)解决线程安全问题

在java中,使用同步解决这个问题

● 同步的好处:解决了线程安全的问题

● 同步的弊端:相对降低了效率,因为同步外的线程都会判断同步锁

● 同步的前提:同步中必须有多个线程并使用同一个锁

① 同步代码块 - 使用的锁可以是任意对象 - 通常在继承Thread中使用

synchronized(obj){//需要被同步的代码;}

② 同步方法 -synchronized作为函数修饰符

普通方法同步:使用的锁是 当前对象 固定的this

静态方法同步:使用的锁是 当前类的Class对象,可以用getClass()方法获取,也可以用当前类名.class表示

public synchronized voidmethod(){// 需要被同步的代码;}

③  同步锁 - 通常在实现Runnable中使用

从Java5开始,Java提供了一种功能更强大的线程同步机制 —— 通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。

Lock提供了比 synchronized 方法和 synchronized代码块 更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的 Condition 对象。

在实现线程安全的控制中,比较常用的是 ReentrantLock(可重入锁)。使用该Lock对象可以显式加锁、释放锁。

● Lock接口(互斥锁):替代了同步代码块或同步函数,将同步的隐式锁操作变成了实现锁操作。同时更为灵活,可以一个锁使用多组监视器。

lock():获取锁

unlock():释放锁,通常需要定义在finally代码块中。

● Condition接口(监视器):将这些监视器方法单独进行了封装,变成了Condition监视器对象。可以任意锁进行组合

await():替代了Object中的 wait 方法。

signal():替代了Object中的 notify 方法。

signalAll():替代了Object中的 notifyAll 方法。

classD {//定义锁对象

private final ReentrantLock lock = newReentrantLock();//...//定义需要保护线程安全的方法

public voidmethod() {//加锁

lock.lock();try{//需要保证线程安全的代码//...method body

} finally{//释放锁

lock.unlock();

}

}

}

5)将上面银行中DrawThread类作如下修改

class DrawThread extendsThread {private Account account;//模拟用户账户

private double money;//操作金额

public DrawThread(String name, Account account, doublemoney) {super(name);this.account =account;this.money =money;

}//多个线程修改同一个共享数据,可能发生线程安全问题

@Overridepublic voidrun() {//使用account作为同步监视器,任何线程在进入下面同步代码块之前//必须先获得account账户的锁定,其他线程无法获得锁,也就无法修改它//这种做法符合:"加锁-修改-释放锁"的逻辑

synchronized(account) {if (account.getBalance() >money) {

System.out.println("【" + getName() + "】取钱:" + " " +money);try{

Thread.sleep(500);

}catch(Exception e) {

e.printStackTrace();

}

account.setBalance(account.getBalance()-money);

System.out.println("\t账户余额为" + " " +account.getBalance());

}else{

System.out.println("【" + getName() + "】取钱:余额不足");

}

}

}

}public classTest {public static voidmain(String[] args) {//初始账户

Account account = new Account("88888888", 1000);//模拟两个线程同时操作账号

new DrawThread("甲", account, 800).start();new DrawThread("乙", account, 800).start();

}

}

再次运行的结果:

cd0a192f2e40259c561c977d73d110c1.png

二、线程死锁(应避免发生)

当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁岀现。

一旦岀现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

死锁是很容易发生的,尤其在系统中出现多个同步监视器的情况下,如下程序将会出现死锁

class Mylock//建立静态锁对象

{public static Object locka = newObject();public static Object lockb = newObject();

}class Test implementsRunnable {private booleanflag;

Test(booleanflag) {this.flag =flag;

}public voidrun() {if (!flag) {while (true) {

System.out.println(Thread.currentThread().getName()+ "...run...");synchronized (Mylock.locka)//线程0持a锁进来时if,

{

System.out.println(Thread.currentThread().getName()+ "...if...locka");synchronized (Mylock.lockb)//线程0持b锁进来时if,线程1持有锁b锁还没有释放,进入阻塞状态

{

System.out.println(Thread.currentThread().getName()+ "...if...lockb");

}

}

}

}else{while (true) {

System.out.println(Thread.currentThread().getName()+ "...run...");synchronized (Mylock.lockb)//线程1持b锁进入else,

{

System.out.println(Thread.currentThread().getName()+ "...elae...lockb");synchronized (Mylock.locka)/// 线程1持a锁进来时else,线程0持有锁a锁还没有释放,进入阻塞状态

{

System.out.println(Thread.currentThread().getName()+ "...elae...locka");

}

}

}

}

}

}classDeadLockTest {public static voidmain(String[] args) {new Thread(new Test(false), "A线程").start();new Thread(new Test(true), "B线程").start();

}

}

运行结果:

0074ed4790f9ee58473674ed2c48236a.png

从运行结果可看到,线程A拿到了a锁,并尝试去获取b锁,与此同时线程B拿到了b锁并尝试去获取a锁,此时线程A和线程B就陷入了无限的等待,形成死锁。

当一个线程永远地持有一个锁,并且其他线程都尝试获得这个锁时,那么它们将永远被阻塞。

1)预防死锁

下面介绍几个常见方法:

① 避免一个线程同时获取多个锁

② 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源

③ 尝试使用定时锁,使用 lock.tryLock 来代替使用内置锁


http://lihuaxi.xjx100.cn/news/269965.html

相关文章

和我一起打造个简单搜索之SpringDataElasticSearch入门

网上大多通过 java 操作 es 使用的都是 TransportClient,而介绍使用 SpringDataElasticSearch 的文章相对比较少,笔者也是摸索了许久,接下来本文介绍 SpringDataElasticSearch 的 api 使用,更加方便的进行查询。 系列文章 一、和我…

精选一套火爆B站的硬核资源,请笑纳!

最近经常有粉丝给我留言求人工智能方向的资源,想要尝试自学人工智能,将自己的一些想法和创意付诸现实,或是想通过自学进入AI领域。细聊之下,大部分人自觉苦于不知从何入手,找不到学习重点。或是止步于晦涩难懂的理论和…

汇总 | 深度学习中图像语义分割基准数据集详解

点击上方“小白学视觉”,选择加"星标"或“置顶”重磅干货,第一时间送达汇总图像语义分割那些质量最好的数据集与常用benchmark数据集前言图像语义分割是计算机视觉最经典的任务之一,早期的图像分割主要有以下几种实现方法。基于像素…

自从上线了 Prometheus 监控告警,真香!

点击上方蓝色“方志朋”,选择“设为星标”回复“666”获取独家整理的学习资料!对很多人来说,未知、不确定、不在掌控的东西,会有潜意识的逃避。当我第一次接触 Prometheus 的时候也有类似的感觉。对初学者来说, Promet…

常用浏览器插件

modify headers :firefox的IP伪造插件 httpRequester:firefox的模拟http请求插件JSON-handle:chrome格式化json插件firebug:firefox查看http请求工具firepath:firefox中获取元素路径转载于:https://www.cnblogs.com/xx…

es dsl java api_ElasticSearch 系列 - RestFulAPI(DSL)

前言DSL全称 Domain Specific language,即特定领域专用语言1.全局操作1.1 查询集群健康情况GET /_cat/health?v ?v表示显示头信息集群的健康状态有红、黄、绿三个状态:绿 – 一切正常(集群功能齐全)黄 – 所有数据可用,但有些副本尚未分配(…

性能超越最新序列推荐模型,华为诺亚方舟提出记忆增强的图神经网络

作者 | Chen Ma, Liheng Ma等译者 | Rachel出品 | AI科技大本营(ID:rgznai100)用户-商品交互的时间顺序可以揭示出推荐系统中用户行为随时间演进的序列性特征。用户与之交互的商品可能受到用户曾经接触的商品的影响。但是,用户和商…

混合云备份利用自定义Workflow保护MySQL的实践

众所周知数据库的保护面临着诸多问题,其中之一就是维护数据底层文件的一致性。除了与数据库应用的深度集成的备份方案(如SAP HANA Backint等),松耦合的通用备份软件较难做到完美的数据库的一致性保护。 为了解决该项痛点&#xff…