本文针对用户取款时多线程并发情境,进行相关多线程控制与优化的描述.
首先建立用户类UserTest、业务操作类SynchronizedTest、数据存取类DataStore,多线程测试类MultiThreadsTest(使用junit的扩展GroboUtils实现)
用户类:UserTest.java
/** * */ package com.eapsoft.core.thread.test; import java.io.Serializable; /** * @author Yuce * */ public class UserTest implements Serializable { /** * */ private static final long serialVersionUID = 3232456789876543L; private Long id; private String account; private Integer money; private String name; public UserTest(Long id, String account, Integer money, String name) { super(); this.id = id; this.account = account; this.money = money; this.name = name; } public UserTest() { super(); // TODO Auto-generated constructor stub } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
数据存取类:DataStore.java
package com.eapsoft.core.thread.test; import java.util.HashMap; import java.util.Map; /** * 在高并发时为了减少数据的读 用缓存来处理数据 * @author Yuce * */ public class DataStore { private static Map<String , UserTest> mapCache = new HashMap<String, UserTest>(); private static DataStore store; private static Byte[] lockObj1 = new Byte[0]; private static Byte[] lockObj2 = new Byte[0]; private DataStore() { super(); // TODO Auto-generated constructor stub } // 为了保证数据的同步,对DataStore进行单例设计且对并发操作进行同步 public static DataStore getStore(){ if(store == null){ synchronized (lockObj1) { if(store == null){ store = new DataStore(); } } } return store; } public void putUserInStore(UserTest user){ mapCache.put(user.getAccount(), user); //DB.saveOrUpdate(user); } /* * 缓存中有查询的对象就从缓存中取出,没有就查询数据库 * 当缓存中没有查询对象,避免并发时多次查询数据库,对其进行同步 */ public UserTest getUserFromStore(String account){ UserTest user = mapCache.get(account); if(user == null){ //为避免死锁情况产生,不要用字符串对象作为对象锁 synchronized (lockObj2) { user = mapCache.get(account); if(user == null){ //数据库查询 db.query(account) if("zhangsan".equalsIgnoreCase(account)){ user =new UserTest(1L , "zhangsan" , 10000 ,"张三" ); } if("lisi".equalsIgnoreCase(account)){ user = new UserTest(2L , "lisi" , 10000 ,"李四" ); } putUserInStore(user); /*** if continue do no synchronized somethings need : ***/ //lockObj2.notify(); //do something................ } } } return user; } }
业务操作类:SynchronizedTest.java 取款:当余额大于等于取款数时,交易成功,否则提示余额不足交易失败
package com.eapsoft.core.thread.test; /** * @author Yuce * */ public class SynchronizedTest { public void opAccountMoney(String account, int getMoney, String execDate) { DataStore store = DataStore.getStore(); UserTest user = store.getUserFromStore(account); if (user != null) { try { user = get(user, 1000); store.putUserInStore(user); } catch (Exception e) { // TODO Auto-generated catch block System.out.println(user.getName() + "余额不足!"); } } } public UserTest get(UserTest user, int money) throws Exception { if (user.getMoney() >= money) { sleepTest(); user.setMoney(user.getMoney() - money); System.out.println("Thread.currentThread().getId() : " + Thread.currentThread().getId() + " " + user.getName() + " 余额: " + user.getMoney()); } else { throw new Exception(); } return user; } private void sleepTest() { try { Thread.currentThread().sleep(100); } catch (InterruptedException e2) { // TODO Auto-generated catch block e2.printStackTrace(); } } }
并发测试类:MultiThreadsTest.java
测试的情境为,并发用户张三与李四的取款,张三与李四账户各有10000,每个用户并发15次,按照我们的取款逻辑应该是2人各有5次取不到款,并提示余额不足
package com.eapsoft.core.thread.test; import java.text.SimpleDateFormat; import java.util.Date; import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner; import net.sourceforge.groboutils.junit.v1.TestRunnable; import org.junit.Test; /** * @author Yuce * */ public class MultiThreadsTest { private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Test public void multiWSReqTest() throws Exception { final String account1 = "zhangsan"; // 构造Runner TestRunnable runner1 = new TestRunnable() { @Override public void runTest() throws Throwable { SynchronizedTest test = new SynchronizedTest(); Date now = new Date(); test.opAccountMoney(account1 , 1000 , df.format(now)); } }; final String account2 = "lisi"; // 构造Runner TestRunnable runner2 = new TestRunnable() { @Override public void runTest() throws Throwable { SynchronizedTest test = new SynchronizedTest(); Date now = new Date(); test.opAccountMoney(account2 , 1000 , df.format(now)); } }; int runnerCount = 30; //Rnner数组,想当于并发多少个。 TestRunnable[] trs = new TestRunnable[runnerCount]; int i = 0; for (; i < runnerCount; i++) { if(i%2 == 0){ trs[i] = runner1; }else{ trs[i] = runner2; } } // 用于执行多线程测试用例的Runner,将前面定义的单个Runner组成的数组传入 MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(trs); try { // 开发并发执行数组里定义的内容 long start = (new Date()).getTime(); mttr.runTestRunnables(); System.out.println("花费时间:" + ((new Date()).getTime() - start) + " 毫秒"); } catch (Throwable e) { e.printStackTrace(); } } }
使用junit test 运行MultiThreadsTest 得到如下信息:
Thread.currentThread().getId() : 9 张三 余额: 9000 Thread.currentThread().getId() : 10 李四 余额: 9000 Thread.currentThread().getId() : 11 张三 余额: 8000 Thread.currentThread().getId() : 12 李四 余额: 8000 Thread.currentThread().getId() : 13 张三 余额: 7000 Thread.currentThread().getId() : 14 李四 余额: 7000 Thread.currentThread().getId() : 15 张三 余额: 6000 Thread.currentThread().getId() : 16 李四 余额: 6000 Thread.currentThread().getId() : 17 张三 余额: 5000 Thread.currentThread().getId() : 18 李四 余额: 5000 Thread.currentThread().getId() : 19 张三 余额: 4000 Thread.currentThread().getId() : 20 李四 余额: 4000 Thread.currentThread().getId() : 21 张三 余额: 3000 Thread.currentThread().getId() : 22 李四 余额: 3000 Thread.currentThread().getId() : 23 张三 余额: 2000 Thread.currentThread().getId() : 24 李四 余额: 2000 Thread.currentThread().getId() : 25 张三 余额: 1000 Thread.currentThread().getId() : 26 李四 余额: 1000 Thread.currentThread().getId() : 28 李四 余额: 0 Thread.currentThread().getId() : 27 张三 余额: 0 Thread.currentThread().getId() : 29 张三 余额: -1000 Thread.currentThread().getId() : 31 张三 余额: -2000 Thread.currentThread().getId() : 32 李四 余额: -2000 Thread.currentThread().getId() : 30 李四 余额: -1000 Thread.currentThread().getId() : 33 张三 余额: -3000 Thread.currentThread().getId() : 35 张三 余额: -4000 Thread.currentThread().getId() : 37 张三 余额: -5000 Thread.currentThread().getId() : 36 李四 余额: -3000 Thread.currentThread().getId() : 34 李四 余额: -4000 Thread.currentThread().getId() : 38 李四 余额: -5000 花费时间:112 毫秒
显然SynchronizedTest中的opAccountMoney要进行同步,首先我们想到的时在方法上同步或者在代码中同步(暂不考虑性能):
public synchronized void opAccountMoney(String account, int getMoney, String execDate) { ......... }
或者
public void opAccountMoney(String account, int getMoney, String execDate) { synchronized(this){ ......... } }
或者
private Byte[] lockObj = new Byte[0]; public void opAccountMoney(String account, int getMoney, String execDate) { synchronized(lockObj){ .... } }
运行测试类,得到信息:
Thread.currentThread().getId() : 15 张三 余额: 8000 Thread.currentThread().getId() : 13 张三 余额: 7000 Thread.currentThread().getId() : 23 张三 余额: 8000 Thread.currentThread().getId() : 11 张三 余额: 6000 Thread.currentThread().getId() : 25 张三 余额: 4000 Thread.currentThread().getId() : 27 张三 余额: 3000 Thread.currentThread().getId() : 29 张三 余额: 2000 Thread.currentThread().getId() : 14 李四 余额: 7000 Thread.currentThread().getId() : 19 张三 余额: 8000 Thread.currentThread().getId() : 31 张三 余额: 1000 Thread.currentThread().getId() : 33 张三 余额: 0 Thread.currentThread().getId() : 10 李四 余额: 9000 Thread.currentThread().getId() : 26 李四 余额: 6000 Thread.currentThread().getId() : 12 李四 余额: 8000 Thread.currentThread().getId() : 9 张三 余额: 8000 Thread.currentThread().getId() : 16 李四 余额: 2000 Thread.currentThread().getId() : 20 李四 余额: 3000 Thread.currentThread().getId() : 18 李四 余额: 3000 Thread.currentThread().getId() : 24 李四 余额: 5000 Thread.currentThread().getId() : 17 张三 余额: 7000 Thread.currentThread().getId() : 37 张三 余额: -2000 Thread.currentThread().getId() : 35 张三 余额: -1000 Thread.currentThread().getId() : 21 张三 余额: 5000 Thread.currentThread().getId() : 28 李四 余额: 0 Thread.currentThread().getId() : 22 李四 余额: 1000 Thread.currentThread().getId() : 34 李四 余额: -2000 Thread.currentThread().getId() : 32 李四 余额: -4000 Thread.currentThread().getId() : 30 李四 余额: -4000 Thread.currentThread().getId() : 36 李四 余额: -2000 Thread.currentThread().getId() : 38 李四 余额: -2000 花费时间:110 毫秒
没有达到同步效果,因为在这3种写法都一个共同特点就是在锁在一个对象内有效,即当同一个对象在不同线程中调用 opAccountMoney时才会达到同步的效果.
要想使其达到同步效果就要:
第1种、保证并发时,所有线程只对同一个业务操作类SynchronizedTest的实例对象调用opAccountMoney
第2种、保证并发时,opAccountMoney方法对所有线程同步
首先介绍第一种,需要改写MultiThreadsTest类
package com.eapsoft.core.thread.test; import java.text.SimpleDateFormat; import java.util.Date; import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner; import net.sourceforge.groboutils.junit.v1.TestRunnable; import org.junit.Test; /** * @author Yuce * */ public class MultiThreadsTest { private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Test public void multiWSReqTest() throws Exception { final String account1 = "zhangsan"; final SynchronizedTest test = new SynchronizedTest();// 构造Runner TestRunnable runner1 = new TestRunnable() { @Override public void runTest() throws Throwable { Date now = new Date(); test.opAccountMoney(account1 , 1000 , df.format(now)); } }; final String account2 = "lisi"; // 构造Runner TestRunnable runner2 = new TestRunnable() { @Override public void runTest() throws Throwable { Date now = new Date(); test.opAccountMoney(account2 , 1000 , df.format(now)); } }; int runnerCount = 30; //Rnner数组,想当于并发多少个。 TestRunnable[] trs = new TestRunnable[runnerCount]; int i = 0; for (; i < runnerCount; i++) { if(i%2 == 0){ trs[i] = runner1; }else{ trs[i] = runner2; } } // 用于执行多线程测试用例的Runner,将前面定义的单个Runner组成的数组传入 MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(trs); try { // 开发并发执行数组里定义的内容 long start = (new Date()).getTime(); mttr.runTestRunnables(); System.out.println("花费时间:" + ((new Date()).getTime() - start) + " 毫秒"); } catch (Throwable e) { e.printStackTrace(); } } }
为了后面的性能说明将:SynchronizedTest.sleepTest 改为sleep(500);
private void sleepTest() { try { Thread.currentThread().sleep(500); } catch (InterruptedException e2) { // TODO Auto-generated catch block e2.printStackTrace(); } }
测试结果:
Thread.currentThread().getId() : 10 李四 余额: 9000 Thread.currentThread().getId() : 37 张三 余额: 9000 Thread.currentThread().getId() : 34 李四 余额: 8000 Thread.currentThread().getId() : 36 李四 余额: 7000 Thread.currentThread().getId() : 38 李四 余额: 6000 Thread.currentThread().getId() : 32 李四 余额: 5000 Thread.currentThread().getId() : 35 张三 余额: 8000 Thread.currentThread().getId() : 30 李四 余额: 4000 Thread.currentThread().getId() : 33 张三 余额: 7000 Thread.currentThread().getId() : 23 张三 余额: 6000 Thread.currentThread().getId() : 25 张三 余额: 5000 Thread.currentThread().getId() : 21 张三 余额: 4000 Thread.currentThread().getId() : 31 张三 余额: 3000 Thread.currentThread().getId() : 27 张三 余额: 2000 Thread.currentThread().getId() : 26 李四 余额: 3000 Thread.currentThread().getId() : 29 张三 余额: 1000 Thread.currentThread().getId() : 17 张三 余额: 0 Thread.currentThread().getId() : 28 李四 余额: 2000 Thread.currentThread().getId() : 24 李四 余额: 1000 张三余额不足! 张三余额不足! 张三余额不足! Thread.currentThread().getId() : 22 李四 余额: 0 张三余额不足! 张三余额不足! 李四余额不足! 李四余额不足! 李四余额不足! 李四余额不足! 李四余额不足! 花费时间:10005 毫秒
第2种:改写SynchronizedTest(这里这介绍一种写法)
private static Byte[] lockObj = new Byte[0]; public void opAccountMoney(String account, int getMoney, String execDate) { synchronized(lockObj){ DataStore store = DataStore.getStore(); // System.out.println(Thread.currentThread().getId() + " : " + cache); UserTest user = store.getUserFromStore(account); if (user != null) { try { user = get(user, 1000); store.putUserInStore(user); /** * if continue do no synchronized somethings need : ** */ // lockObj.notify(); // do something................ } catch (Exception e) { // TODO Auto-generated catch block System.out.println(user.getName() + "余额不足!"); } } } }
运行测试类:
Thread.currentThread().getId() : 10 李四 余额: 9000 Thread.currentThread().getId() : 34 李四 余额: 8000 Thread.currentThread().getId() : 32 李四 余额: 7000 Thread.currentThread().getId() : 36 李四 余额: 6000 Thread.currentThread().getId() : 30 李四 余额: 5000 Thread.currentThread().getId() : 38 李四 余额: 4000 Thread.currentThread().getId() : 28 李四 余额: 3000 Thread.currentThread().getId() : 37 张三 余额: 9000 Thread.currentThread().getId() : 35 张三 余额: 8000 Thread.currentThread().getId() : 33 张三 余额: 7000 Thread.currentThread().getId() : 26 李四 余额: 2000 Thread.currentThread().getId() : 24 李四 余额: 1000 Thread.currentThread().getId() : 31 张三 余额: 6000 Thread.currentThread().getId() : 22 李四 余额: 0 Thread.currentThread().getId() : 29 张三 余额: 5000 李四余额不足! Thread.currentThread().getId() : 27 张三 余额: 4000 Thread.currentThread().getId() : 25 张三 余额: 3000 Thread.currentThread().getId() : 23 张三 余额: 2000 Thread.currentThread().getId() : 21 张三 余额: 1000 Thread.currentThread().getId() : 19 张三 余额: 0 李四余额不足! 李四余额不足! 张三余额不足! 李四余额不足! 张三余额不足! 张三余额不足! 张三余额不足! 李四余额不足! 张三余额不足! 花费时间:10004 毫秒
以上2种的运行效率都差不多。
优化篇:
我们分析业务:我们控制并发实际出发点是为了控制同一个用户在并发时同步,而不是为了同步所有的用户,只需要同步同一个用户即可。以上的2种写法都是同步了所有的用户,颗粒度太粗,性能差.
如何同步同一个用户呢,我们只要保证对同一个用户并发时,操作的是这个用户的同一个实例对象,且以此实例对象为对象锁即可。如何保证并发时多线程操作同一用户只操作该用户的一个实例对象呢。在DataStore类种其实已经给出了实现,使用缓存对用户实例对象的获取与存放.为什么使用缓存呢.修改MultiThreadsTest,在多线程并发之前将张三、李四的用户实例查询出来,修改opAccountMoney方法的参数,账户String改为user对象,然后在opAccountMoney中使用user对象为对象锁,即:
修改MultiThreadsTest:
package com.eapsoft.core.thread.test; import java.text.SimpleDateFormat; import java.util.Date; import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner; import net.sourceforge.groboutils.junit.v1.TestRunnable; import org.junit.Test; /** * @author Yuce * */ public class MultiThreadsTest { private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Test public void multiWSReqTest() throws Exception { String account1 = "zhangsan"; String account2 = "lisi"; DataStore store = DataStore.getStore(); final SynchronizedTest test = new SynchronizedTest(); final UserTest user1 = store.getUserFromStore(account1); final UserTest user2 = store.getUserFromStore(account2); // 构造Runner TestRunnable runner1 = new TestRunnable() { @Override public void runTest() throws Throwable { //SynchronizedTest test = new SynchronizedTest(); Date now = new Date(); test.opAccountMoney(user1 , 1000 , df.format(now)); } }; // 构造Runner TestRunnable runner2 = new TestRunnable() { @Override public void runTest() throws Throwable { //SynchronizedTest test = new SynchronizedTest(); Date now = new Date(); test.opAccountMoney(user2 , 1000 , df.format(now)); } }; int runnerCount = 30; //Rnner数组,想当于并发多少个。 TestRunnable[] trs = new TestRunnable[runnerCount]; int i = 0; for (; i < runnerCount; i++) { if(i%2 == 0){ trs[i] = runner1; }else{ trs[i] = runner2; } } // 用于执行多线程测试用例的Runner,将前面定义的单个Runner组成的数组传入 MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(trs); try { // 开发并发执行数组里定义的内容 long start = (new Date()).getTime(); mttr.runTestRunnables(); System.out.println("花费时间:" + ((new Date()).getTime() - start) + " 毫秒"); } catch (Throwable e) { e.printStackTrace(); } } }
修改opAccountMoney方法
public void opAccountMoney(UserTest user, int getMoney, String execDate) { synchronized(user){ DataStore store = DataStore.getStore(); user = store.getUserFromStore(user.getAccount()); if (user != null) { try { user = get(user, 1000); store.putUserInStore(user); /** * if continue do no synchronized somethings need : ** */ // user.notify(); // do something................ } catch (Exception e) { // TODO Auto-generated catch block System.out.println(user.getName() + "余额不足!"); } } } }
为了保证数据的准确,在获得对象锁执行方法时需要进行将user再次使用取出,如果不使用缓存就需要再次从数据库取出,user = store.getUserFromStore(user.getAccount());这将增加业务操作的执行时间,并且各种ORM组件从数据库取出的用户实例对象与参数中的user对象不为同一对象,直接=号赋值,这将引发锁失效,必须进行复制赋值.所以为了避免多余的操作和减少数据库的压力,故这里使用缓存来存取对象.
运行结果:
Thread.currentThread().getId() : 9 张三 余额: 9000 Thread.currentThread().getId() : 10 李四 余额: 9000 Thread.currentThread().getId() : 35 张三 余额: 8000 Thread.currentThread().getId() : 38 李四 余额: 8000 Thread.currentThread().getId() : 31 张三 余额: 7000 Thread.currentThread().getId() : 36 李四 余额: 7000 Thread.currentThread().getId() : 33 张三 余额: 6000 Thread.currentThread().getId() : 24 李四 余额: 6000 Thread.currentThread().getId() : 37 张三 余额: 5000 Thread.currentThread().getId() : 26 李四 余额: 5000 Thread.currentThread().getId() : 29 张三 余额: 4000 Thread.currentThread().getId() : 32 李四 余额: 4000 Thread.currentThread().getId() : 25 张三 余额: 3000 Thread.currentThread().getId() : 30 李四 余额: 3000 Thread.currentThread().getId() : 27 张三 余额: 2000 Thread.currentThread().getId() : 28 李四 余额: 2000 Thread.currentThread().getId() : 21 张三 余额: 1000 Thread.currentThread().getId() : 34 李四 余额: 1000 Thread.currentThread().getId() : 23 张三 余额: 0 张三余额不足! 张三余额不足! 张三余额不足! 张三余额不足! 张三余额不足! Thread.currentThread().getId() : 22 李四 余额: 0 李四余额不足! 李四余额不足! 李四余额不足! 李四余额不足! 李四余额不足! 花费时间:5002 毫秒
时间减少一半.
其次使用缓存还有其它原因,在一个方法的接口一旦确定后,不可随便更改此方法的参数,也就是我们在实际中有时不可能有权限去更改opAccountMoney方法的参数,可能这个方法是其它的子系统等.这时opAccountMoney方法如何保存user对象的唯一性就需要缓存或者其它方式来维持。
最终代码为:
测试类MultiThreadsTest
package com.eapsoft.core.thread.test; import java.text.SimpleDateFormat; import java.util.Date; import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner; import net.sourceforge.groboutils.junit.v1.TestRunnable; import org.junit.Test; /** * @author Yuce * */ public class MultiThreadsTest { private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Test public void multiWSReqTest() throws Exception { final String account1 = "zhangsan"; final SynchronizedTest test = new SynchronizedTest(); // 构造Runner TestRunnable runner1 = new TestRunnable() { @Override public void runTest() throws Throwable { //SynchronizedTest test = new SynchronizedTest(); Date now = new Date(); test.opAccountMoney(account1 , 1000 , df.format(now)); } }; final String account2 = "lisi"; // 构造Runner TestRunnable runner2 = new TestRunnable() { @Override public void runTest() throws Throwable { //SynchronizedTest test = new SynchronizedTest(); Date now = new Date(); test.opAccountMoney(account2 , 1000 , df.format(now)); } }; int runnerCount = 30; //Rnner数组,想当于并发多少个。 TestRunnable[] trs = new TestRunnable[runnerCount]; int i = 0; for (; i < runnerCount; i++) { if(i%2 == 0){ trs[i] = runner1; }else{ trs[i] = runner2; } } // 用于执行多线程测试用例的Runner,将前面定义的单个Runner组成的数组传入 MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(trs); try { // 开发并发执行数组里定义的内容 long start = (new Date()).getTime(); mttr.runTestRunnables(); System.out.println("花费时间:" + ((new Date()).getTime() - start) + " 毫秒"); } catch (Throwable e) { e.printStackTrace(); } } }
业务操作类:
/** * @author Yuce * */ public class SynchronizedTest { public void opAccountMoney(String account, int getMoney, String execDate) { DataStore store = DataStore.getStore(); // System.out.println(Thread.currentThread().getId() + " : " + cache); UserTest user = store.getUserFromStore(account); if (user != null) { synchronized (user) { try { user = get(user, 1000); store.putUserInStore(user); /** * if continue do no synchronized somethings need : ** */ // lockObj.notify(); // do something................ } catch (Exception e) { // TODO Auto-generated catch block System.out.println(user.getName() + "余额不足!"); } } } } public UserTest get(UserTest user, int money) throws Exception { if (user.getMoney() >= money) { sleepTest(); user.setMoney(user.getMoney() - money); System.out.println("Thread.currentThread().getId() : " + Thread.currentThread().getId() + " " + user.getName() + " 余额: " + user.getMoney()); } else { throw new Exception(); } return user; } private void sleepTest() { try { Thread.currentThread().sleep(500); } catch (InterruptedException e2) { // TODO Auto-generated catch block e2.printStackTrace(); } } }
DataStore.java不变.
运行:
Thread.currentThread().getId() : 9 张三 余额: 9000 Thread.currentThread().getId() : 10 李四 余额: 9000 Thread.currentThread().getId() : 20 李四 余额: 8000 Thread.currentThread().getId() : 33 张三 余额: 8000 Thread.currentThread().getId() : 38 李四 余额: 7000 Thread.currentThread().getId() : 37 张三 余额: 7000 Thread.currentThread().getId() : 31 张三 余额: 6000 Thread.currentThread().getId() : 36 李四 余额: 6000 Thread.currentThread().getId() : 29 张三 余额: 5000 Thread.currentThread().getId() : 34 李四 余额: 5000 Thread.currentThread().getId() : 32 李四 余额: 4000 Thread.currentThread().getId() : 23 张三 余额: 4000 Thread.currentThread().getId() : 27 张三 余额: 3000 Thread.currentThread().getId() : 28 李四 余额: 3000 Thread.currentThread().getId() : 30 李四 余额: 2000 Thread.currentThread().getId() : 25 张三 余额: 2000 Thread.currentThread().getId() : 26 李四 余额: 1000 Thread.currentThread().getId() : 21 张三 余额: 1000 Thread.currentThread().getId() : 17 张三 余额: 0 Thread.currentThread().getId() : 24 李四 余额: 0 张三余额不足! 张三余额不足! 李四余额不足! 张三余额不足! 李四余额不足! 张三余额不足! 李四余额不足! 张三余额不足! 李四余额不足! 李四余额不足! 花费时间:5002 毫秒
本文若有错误之处还请各位大大指出