银行取款[多线程]{使用ThreadLocal管理共享变量,但此场景并不保证线程同步}

简介: 经典例子:老婆(朱丽叶)老公(罗密欧),使用银行卡和存折,或者网银等,同时对同一账户操作的安全问题。 此处用多线程实现,同时取款的模拟实现,使用ThreadLocal管理共享变量,但此场景并不保证线程同步,查看取款安全隐患问题,代码如下: -------------------------------------------------------------------------

经典例子:老婆(朱丽叶)老公(罗密欧),使用银行卡和存折,或者网银等,同时对同一账户操作的安全问题。

此处用多线程实现,同时取款的模拟实现,使用ThreadLocal管理共享变量,但此场景并不保证线程同步,查看取款安全隐患问题,代码如下:

-------------------------------------------------------------------------------------------------------------------------------------

ThreadLocal:一般每一个Thread线程将对应一个ThreadLocal副本,如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立(独立操作),这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

-------------------------------------------------------------------------------------------------------------------------------------

银行账户:

package com.tsxs.bank;

public class ThreadLocalBankAccount {
	//使用ThreadLocal类管理共享的ThreadLocalBankAccount变量
	  //余额
	private static ThreadLocal<Integer> balance = new ThreadLocal<Integer>(){
		@Override
		protected Integer initialValue() {
			return 500;};
	};
	//查询
	public int getBalance(){
		return balance.get();
	}
	//取款
	public void withdraw(int amount){
		balance.set(balance.get() - amount);
	}
	//存款
	public void deposit(int amount){
		balance.set(balance.get() + amount);
	}
}

ThreadLocal管理类:

package com.tsxs.syncmethods;

import com.tsxs.bank.ThreadLocalBankAccount;


public class ThreadLocalManager  implements Runnable{
	//所有Thread多线程线程都共享Runnable(接口对象)和account对象
	private ThreadLocalBankAccount account = new ThreadLocalBankAccount();
	@Override
	public void run() {
		for(int i = 0; i< 5; i++){			//总共取款5次
			makeWithdraw(100);			//每次取款100
			if(account.getBalance() < 0){
				System.out.println(""+Thread.currentThread().getName()+"   透支了!");
			}
		}
	}
	
	/**
	 * makeWithdraw 账户取款
	 * @param amount 取款金额<br />
	 * 打印log记录取款过程
	 * */
	private void makeWithdraw(int amount){
		if(account.getBalance() >= amount){			//如果余额足够则取款
			System.out.println(""+Thread.currentThread().getName()+"   准备取款!");
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				System.out.println(Thread.currentThread().getName()+"   准备取款,等待0.5s线程中断!"+e.getMessage());
			}
			account.withdraw(amount);
			System.out.println(""+Thread.currentThread().getName()+"   完成"+amount+"取款!余额为"+account.getBalance());
		}else{			//余额不足则提示
			System.out.println(""+"余额不足以支付"+Thread.currentThread().getName()+amount+"   的取款,余额为"+account.getBalance());
		}
	}
}


测试代码:

package com.tsxs.test;

import org.junit.Test;

import com.tsxs.syncmethods.CodeLock;
import com.tsxs.syncmethods.NoSync;
import com.tsxs.syncmethods.SyncBlock;
import com.tsxs.syncmethods.SyncMethod;
import com.tsxs.syncmethods.ThreadLocalManager;
import com.tsxs.syncmethods.VolatileField;

public class TreadSyncTest {

//	@Test
//	public void test() {
/*Junit不适合多线程并发测试。
    因为线程还在激活状态的时候,Junit已经执行完成。
	在Junit的TestRunner中,它没有被设计成搜寻Runnable实例,
	并且等待这些线程发出报告,它只是执行它们并且忽略了它们的存在。
	综上,不可能在Junit中编写和维护多线程的单元测试。
}*/	
	public static void main(String[] args) {
		//实现Runnable:所有Thread多线程线程都共享Runnable(接口对象)
//		NoSync target =new NoSync();
//		SyncMethod target = new SyncMethod();
//		SyncBlock target = new SyncBlock();
//		CodeLock target = new CodeLock();
//		VolatileField target = new VolatileField();
		ThreadLocalManager target = new ThreadLocalManager();
		//创建李琦和他老婆两个线程实现取款(同时)
		Thread lq = new Thread(target);
		lq.setName("罗密欧");
		Thread lqwf = new Thread(target);
		lqwf.setName("朱丽叶");
		//调用Thread对象的start()方法,启动线程,执行run()方法(OS)
		lq.start();
		lqwf.start();
	}
}


测试结果:

罗密欧   准备取款!
朱丽叶   准备取款!
朱丽叶   完成100取款!余额为400
罗密欧   完成100取款!余额为400
朱丽叶   准备取款!
罗密欧   准备取款!
罗密欧   完成100取款!余额为300
朱丽叶   完成100取款!余额为300
罗密欧   准备取款!
朱丽叶   准备取款!
罗密欧   完成100取款!余额为200
朱丽叶   完成100取款!余额为200
罗密欧   准备取款!
朱丽叶   准备取款!
朱丽叶   完成100取款!余额为100
罗密欧   完成100取款!余额为100
朱丽叶   准备取款!
罗密欧   准备取款!
朱丽叶   完成100取款!余额为0
罗密欧   完成100取款!余额为0

分析结果:

双线程总共取款10次,账户总额为500.

取款结果:取款成功1000元,余额显示不对,最终账户余额为0,已透支一倍。

现实中,此取款代码是有严重bug的,上边数据对于银行是危险的,个人也会带来不必要的麻烦。

多线程访问安全保证!

 

目录
相关文章
|
2月前
|
Java 开发者
解锁并发编程新姿势!深度揭秘AQS独占锁&ReentrantLock重入锁奥秘,Condition条件变量让你玩转线程协作,秒变并发大神!
【8月更文挑战第4天】AQS是Java并发编程的核心框架,为锁和同步器提供基础结构。ReentrantLock基于AQS实现可重入互斥锁,比`synchronized`更灵活,支持可中断锁获取及超时控制。通过维护计数器实现锁的重入性。Condition接口允许ReentrantLock创建多个条件变量,支持细粒度线程协作,超越了传统`wait`/`notify`机制,助力开发者构建高效可靠的并发应用。
78 0
|
2天前
|
Java Linux
【网络】高并发场景处理:线程池和IO多路复用
【网络】高并发场景处理:线程池和IO多路复用
11 2
|
3天前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
1月前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
|
1月前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。
|
2月前
|
Java
多线程线程同步
多线程的锁有几种方式
|
3月前
|
存储 SQL Java
(七)全面剖析Java并发编程之线程变量副本ThreadLocal原理分析
在之前的文章:彻底理解Java并发编程之Synchronized关键字实现原理剖析中我们曾初次谈到线程安全问题引发的"三要素":多线程、共享资源/临界资源、非原子性操作,简而言之:在同一时刻,多条线程同时对临界资源进行非原子性操作则有可能产生线程安全问题。
|
2月前
|
缓存 Java 容器
多线程环境中的虚假共享是什么?
【8月更文挑战第21天】
27 0
|
2月前
|
安全 Java
【多线程面试题 六】、 如何实现线程同步?
实现线程同步的方法包括同步方法、同步代码块、使用ReentrantLock、volatile关键字以及原子变量类,以确保线程安全和数据一致性。
|
14天前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口

热门文章

最新文章