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

多线程编程(四)--线程同步

       当使用多个线程来訪问同一个数据时,就easy出现线程安全的问题。比如,银行取钱。当我们去自己主动取款机取钱时。正好还有一个人转账,即多个线程改动同一数据,这时就easy出现线程安全问题。


线程安全

/**
 * 账户类。该类封装了账户编号和剩余金额两个属性
 * @author Emily-T
 *
 */
public class Account {

	//账户编号
	private String accountNo;
	//剩余金额
	private double balance;
	public Account(){}
	
	//构造函数
	public Account(String accountNo,double balance){
		this.accountNo = accountNo;
		this.balance = balance;
	}
	
	//以下两个方法依据accountNo来计算Account的hashCode和推断equals
	public int hashCode(){
		return accountNo.hashCode();
	}
	
	public boolean equals(Object obj){
		if (obj != null && obj.getClass() == Account.class) {
			Account target = (Account) obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}

	public String getAccountNo() {
		return accountNo;
	}

	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}

	public double getBalance() {
		return balance;
	}

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

/**
 * 取钱的线程类
 * 
 * @author Emily-T
 *
 */
public class DrawThread extends Thread {

	// 模拟用户账户
	private Account account;

	// 当前取钱线程所希望取的钱数
	private double drawAmount;

	public DrawThread(String name, Account account, double drawAmount) {
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}

	// 当多条线程改动同一个共享数据时,将涉及数据安全问题
	public void run() {
		// 账户剩余金额大于取钱数目
		if (account.getBalance() >= drawAmount) {

			// 吐出钞票
			System.out.println("取钱成功!吐出钞票:" + drawAmount);

//			try {
//				Thread.sleep(1);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
			// 改动剩余金额
			account.setBalance(account.getBalance() - drawAmount);
			System.out.println("\t剩余金额为:" + account.getBalance());
		} else {
			System.out.println(getName() + "取钱失败!剩余金额不足!");
		}
	}
}
/**
 * 启动两个线程
 * @author Emily-T
 *
 */
public class TestDraw {

	public static void main(String[] args){
		//创建一个账户
		Account acct = new Account("1234567",1000);
		//模拟两个线程对同一个账户取钱
		new DrawThread("甲",acct,800).start();
		new DrawThread("乙",acct,800).start();
	}
}
结果:

        

      从结果看来。账户剩余金额仅仅有1000,取出了1600元,剩下-200元。出现这样的结果是由于run方法的方法体不具有同步安全性,程序中有两条并发线程在改动Account对象。


线程同步


改动例如以下:加上同步代码块:

// 当多条线程改动同一个共享数据时,将涉及数据安全问题
	public void run() {

		// 使用account作为同步监视器。不论什么线程进入以下同步代码块之前。必须先获得
		// 对account账户的锁定——其它线程无法获得锁,也就是无法改动它
		// 加锁——改动完毕——释放锁

		synchronized (account) {
			// 账户剩余金额大于取钱数目
			if (account.getBalance() >= drawAmount) {

				// 吐出钞票
				System.out.println("取钱成功!

吐出钞票:" + drawAmount); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 改动剩余金额 account.setBalance(account.getBalance() - drawAmount); System.out.println("\t剩余金额为:" + account.getBalance()); } else { System.out.println(getName() + "取钱失败。剩余金额不足!"); } } }


结果:

       

        不论什么时刻仅仅能有一条线程能够获得对同步监视器的锁定,当同步代码块运行结束后,该线程自然释放了对该同步监视器的锁定。

 

        同步监视器的目的:阻止两条线程对同一个共享资源进行并发訪问。因此通常推荐使用可能被并发訪问的共享资源充当同步监视器。

 

        可变类的线程安全是以降低程序的执行效率为代价的。为了降低程序安全所带来的负面影响。程序能够採用例如以下策略:

          1、不要对线程安全类的全部方法都进行同步,仅仅对那些会改变竞争资源的方法进行同步

          2、假设可变类有两种执行环境:单线程和多线程环境。则应该为该可变类提供两种版本号:线程不安全版本号和线程安全版本号。在单线程环境中使用线程不安全版本号以保证性能,在多线程环境中使用线程安全版本号。

相关文章:

  • P4389 付公主的背包
  • 通过反射将数据库数据输入到指定类
  • java 中类似于goto语句的语法
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • iOS网络-NSURLSession/AFNetworking发送HTTPS网络请求
  • 打印机连接常见故障
  • -----二叉树的遍历-------
  • Highcharts tooltip显示数量和百分比
  • Listen第二个参数的意义
  • 发布mvc报错:403.14-Forbidden Web 服务器被配置为不列出此目录的内容
  • 在Unity中实现一个简单的消息管理器
  • 重要的方法
  • Docker入门最佳实践
  • webpack 4.x 搭建项目脚手架
  • python学习第十二课
  • [deviceone开发]-do_Webview的基本示例
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • 【Linux系统编程】快速查找errno错误码信息
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • bootstrap创建登录注册页面
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • Odoo domain写法及运用
  • 从setTimeout-setInterval看JS线程
  • 服务器从安装到部署全过程(二)
  • 如何用vue打造一个移动端音乐播放器
  • 入手阿里云新服务器的部署NODE
  • 双管齐下,VMware的容器新战略
  • 它承受着该等级不该有的简单, leetcode 564 寻找最近的回文数
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • UI设计初学者应该如何入门?
  • ​批处理文件中的errorlevel用法
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • ${factoryList }后面有空格不影响
  • $jQuery 重写Alert样式方法
  • (2)(2.10) LTM telemetry
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (八)五种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (附源码)ssm高校升本考试管理系统 毕业设计 201631
  • (论文阅读26/100)Weakly-supervised learning with convolutional neural networks
  • (实战篇)如何缓存数据
  • (一)VirtualBox安装增强功能
  • (转)树状数组
  • *2 echo、printf、mkdir命令的应用
  • *p++,*(p++),*++p,(*p)++区别?
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .gitignore文件_Git:.gitignore
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项
  • /proc/interrupts 和 /proc/stat 查看中断的情况
  • ::前边啥也没有
  • @JoinTable会自动删除关联表的数据
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节