当前位置: 首页 > news >正文

【多线程】线程安全与线程同步

线程安全与线程同步

1.什么是线程安全问题?

多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题

取钱的线程安全问题场景:

两个人他们有一个共同的账户,余额是10万元,如果两个人同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题?
(1)线程安全问题出现的原因:

  • 存在多个线程在同时执行
  • 同时访问一个共享资源
  • 存在修改该共享资源
    在这里插入图片描述

2.线程同步

线程同步就是解决线程安全问题的方案

(1)线程同步的思想:让多个线程实现先后依次访问共享资源,这样就解决了安全问题

(2)线程同步的常见方案

  • 加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来

(3)线程同步方式

  • 方式一:同步代码块

    ①作用:把访问共享资源的核心代码给上锁,以此保证线程的安全

    ②原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行

    ③同步锁的注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug

    ④锁对象不能随便选择一个唯一的对象,会影响其他无关线程的执行,例如String字符串

    ⑤锁对象的使用规范

    • 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象
    • 对于静态方法建议使用字节码(类名.class)对象作为锁对象
public class Demo {public static void main(String[] args) {//3、创建账户对象(共享数据)Account acc = new Account("9527", 100000);//4、两个线程同时取款(多个线程操作共享数据,出现线程安全问题)new MyThread(acc, "小明").start();new MyThread(acc, "小红").start();}
}
//2、线程类,封装取款的代码
class MyThread extends Thread {private Account acc;public MyThread(Account acc, String name) {super(name);this.acc = acc;}@Overridepublic void run() {//取钱时,要传递取款金额acc.drawMoney(100000);}
}//1、账户类,包含取款功能
class Account {//卡号private String cardID;//余额private double money;public Account() {}public Account(String cardID, double money) {this.cardID = cardID;this.money = money;}public String getCardID() {return cardID;}public void setCardID(String cardID) {this.cardID = cardID;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}//取款功能,参数money为取款金额public void drawMoney(double money) {//获取当前线程对象的名称String name = Thread.currentThread().getName();synchronized (this) {if (this.money >= money) {System.out.println(name + "取钱:" + money + "成功");this.money -= money;System.out.println(name + "取钱后余额为:" + this.money);} else {System.out.println(name + "取钱失败,余额不足");}}}//对于静态方法建议使用字节码(类名.class)对象作为锁对象public static void test(){synchronized (Account.class){}}
}

在这里插入图片描述

  • 方式二:同步方法

    ①作用:把访问共享资源的核心方法上锁,以此保证线程安全

    ②原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行

    ③同步方法的底层原理

    • 同步方法其实底层也是由隐式锁对象的,只是锁的范围是整个方法代码
    • 如果方法是实例方法:同步方法默认this作为锁的对象
    • 如果方法是静态方法:同步方法默认用类名.class作为锁对象
//线程类和测试类同上
//1、账户类,包含取款功能
class Account {//卡号private String cardID;//余额private double money;public Account() {}public Account(String cardID, double money) {this.cardID = cardID;this.money = money;}public String getCardID() {return cardID;}public void setCardID(String cardID) {this.cardID = cardID;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}//取款功能,参数money为取款金额public synchronized void drawMoney(double money) {String name = Thread.currentThread().getName();if (this.money >= money) {System.out.println(name + "取钱:" + money + "成功");this.money -= money;System.out.println(name + "取钱后余额为:" + this.money);} else {System.out.println(name + "取钱失败,余额不足");}}//静态方法public synchronized static void test(){}
}

④同步代码块和同步方法的区别

同步代码块:锁对象可以指定,锁的范围可以指定,性能高且灵活

同步方法:锁对象不能指定,锁的是方法体,阅读性更高

  • 方式三:Lock锁

    ①Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大

    ②Lock是接口,不能直接被实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象

    public ReentrantLock():获得Lock锁的实现类对象

    ③Lock的常用方法

    • void lock():获得锁
    • void unlock():释放锁

    ④Lock锁使用规范

    • 锁对象创建在成员位置,使用final修饰
    • 释放锁的代码写在finally块中
    public class Demo {public static void main(String[] args) {//3、创建账户对象(共享数据)Account acc = new Account("9527", 100000);//4、两个线程同时取款(多个线程操作共享数据,出现线程安全问题)new MyThread(acc, "小明").start();new MyThread(acc, "小红").start();}
    }
    //2、线程类,封装取款的代码
    class MyThread extends Thread {private Account acc;public MyThread(Account acc, String name) {super(name);this.acc = acc;}@Overridepublic void run() {//取钱时,要传递取款金额acc.drawMoney(100000);}
    }
    //1、账户类,包含取款功能
    class Account {//卡号private String cardID;//余额private double money;//规范1、锁对象创建在成员位置,使用final修饰private final ReentrantLock lock = new ReentrantLock();public Account() {}public Account(String cardID, double money) {this.cardID = cardID;this.money = money;}public String getCardID() {return cardID;}public void setCardID(String cardID) {this.cardID = cardID;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}//取款功能,参数money为取款金额public void drawMoney(double money) {String name = Thread.currentThread().getName();try {//上锁lock.lock();if (this.money >= money) {System.out.println(name + "取钱:" + money + "成功");this.money -= money;System.out.println(name + "取钱后余额为:" + this.money);} else {System.out.println(name + "取钱失败,余额不足");}} catch (Exception e) {e.printStackTrace();} finally {//释放锁lock.unlock();}}
    }
    
http://www.lryc.cn/news/161941.html

相关文章:

  • 指针权限,new与delete,类与对象,函数模板,类模板的用法
  • Unity——脚本与序列化
  • NJ求职盘点
  • 01卡特兰数
  • 若依前端vue设置子路径
  • Vue中使用pdf.js实现在线预览pdf文件流
  • 态、势、感、知与时空、关系
  • D. Paths on the Tree
  • CocosCreator3.8研究笔记(九)CocosCreator 场景资源的理解
  • 大数据课程L1——网站流量项目的概述整体架构
  • 提升数据库安全小技巧,使用SSH配合开源DBeaver工具连接数据库
  • 信息安全技术概论-李剑-持续更新
  • java项目基于 SSM+JSP 的人事管理系统
  • 【Node.js】—基本知识点总结
  • Leetcode.174 地下城游戏
  • python实现adb辅助点击屏幕工具
  • 智能合约安全分析,针对 ERC777 任意调用合约 Hook 攻击
  • nodejs 爬虫 axios 异步爬虫 教程 【一】
  • Swift学习笔记三(Dictionary 篇)
  • javax.mail 遇到501 mail from address must be same as authorization user 的問題
  • 【Python】网络编程
  • 客户端开发常用框架
  • 数据分析综述
  • 区块链技术与应用 - 学习笔记2【密码学基础】
  • 制作Linux发行版安装镜像:复刻centos镜像安装ISO
  • 【复习socket】每天40min,我们一起用70天稳扎稳打学完《JavaEE初阶》——29/70 第二十九天
  • postgresql-常用数学函数
  • Docker实战技巧(一):常用命令与最佳实践
  • 使用CUDA计算GPU的理论显存带宽
  • npm install依赖冲突解决办法