经典例子:老婆(朱丽叶)老公(罗密欧),使用银行卡和存折,或者网银等,同时对同一账户操作的安全问题。
此处用多线程实现,同时取款的模拟实现,使用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的,上边数据对于银行是危险的,个人也会带来不必要的麻烦。
多线程访问安全保证!