当前位置:   article > 正文

Java实现多线程操作多账户_多线程转账问题

多线程转账问题

前言

某公司一个面试题:

1.有二十个账户,每个账户初始余额10000元。

2.有十个转账线程,对二十个账户中的两个随机选取账户进行转账,转账额度100以内正整数随机数。

3.每个线程执行100次转账操作。

4.最后请打印出二十个账户的余额。

正好很久没有做这类型题了,拿来练练手,结果碰到了一些问题。

正文

方案一:

首先描述下思路,首先用一个List数组存20个账户,然后对每个账户赋初值10000,在新建10个转账线程,对List数组中20个账户进行并发操作,每个线程分别获取两个账户进行随机额度转账。那么最简单的方式就是对List数组进行加锁就可以了。代码实现如下:

  1. public class test {
  2. static int num;
  3. public static void main(String[] args) {
  4. List<Integer> accountList = new ArrayList<Integer>();
  5. for (int i = 0;i < 20;i++){
  6. accountList.add(10000);
  7. }
  8. System.out.println(num);
  9. MulThreadTest mulThreadTest = new MulThreadTest(accountList);
  10. ThreadGroup threadGroup =new ThreadGroup("ThreadGroup1");
  11. for (int i = 0;i < 10;i++){
  12. Thread thread = new Thread(mulThreadTest);
  13. thread.setName("thread="+i);
  14. thread.start();
  15. }
  16. try{
  17. Thread.sleep(5000);
  18. }catch(Exception e){
  19. e.printStackTrace();
  20. }
  21. int sum = 0;
  22. for (int i = 0;i < 20;i++){
  23. sum = sum + accountList.get(i);
  24. System.out.println(accountList.get(i));
  25. }
  26. System.out.println(sum);
  27. }
  28. }
  1. public class MulThreadTest implements Runnable{
  2. private List<Integer> accountList;
  3. private int first;
  4. private int second;
  5. private int num;
  6. public MulThreadTest(List accountList){
  7. this.accountList = accountList;
  8. }
  9. @Override
  10. public void run() {
  11. synchronized (accountList){
  12. for(int i = 0;i < 100;i ++) {
  13. first = new Random().nextInt(20);
  14. second = new Random().nextInt(20);
  15. if(first == second){
  16. if(second < 19){
  17. second += 1;
  18. }else {
  19. second = 1;
  20. }
  21. }
  22. num = new Random().nextInt(100);
  23. if(accountList.get(first) - num < 0){
  24. i --;
  25. continue;
  26. }
  27. System.out.println(Thread.currentThread().getName()+",操作前:"+"账户:"+first+"="+accountList.get(first)+"账户:"+second+"="+accountList.get(second));
  28. accountList.set(first,accountList.get(first) - num);
  29. accountList.set(second,accountList.get(second) + num);
  30. System.out.println(Thread.currentThread().getName()+",操作:"+"账户:"+first+"="+accountList.get(first)+"=>"+num+"=>"+"账户:"+second+"="+accountList.get(second));
  31. }
  32. }
  33. }
  34. }

以下是20个账户的金额以及总金额

  1. 10114
  2. 10046
  3. 10268
  4. 10676
  5. 10250
  6. 10344
  7. 9899
  8. 10120
  9. 9629
  10. 10145
  11. 9211
  12. 9761
  13. 9903
  14. 10187
  15. 9374
  16. 10508
  17. 10046
  18. 10005
  19. 9529
  20. 9985
  21. 200000

以上可以解决这个转账并发问题,但是锁的粒度还是有点大了,在一个线程操作List时其它线程是没法操作的,那么如何减小锁的粒度,实现更高的效率呢?

方案二:

在方案一的基础上,把锁的对象修改为List中的某个账户,这样当其中一个线程在操作某个账户时,其它线程就仅仅无法操作该账户,影响范围能减小很多。代码实现如下:

  1. public class test {
  2. static int num;
  3. public static void main(String[] args) {
  4. List<Account> accountList = new ArrayList<Account>();
  5. for (int i = 0;i < 20;i++){
  6. accountList.add(new Account(10000));
  7. }
  8. System.out.println(num);
  9. MulThreadTest1 mulThreadTest = new MulThreadTest1(accountList);
  10. ThreadGroup threadGroup =new ThreadGroup("ThreadGroup1");
  11. for (int i = 0;i < 10;i++){
  12. Thread thread = new Thread(mulThreadTest);
  13. thread.setName("thread="+i);
  14. thread.start();
  15. }
  16. try{
  17. Thread.sleep(5000);
  18. }catch(Exception e){
  19. e.printStackTrace();
  20. }
  21. int sum = 0;
  22. for (int i = 0;i < 20;i++){
  23. sum = sum + accountList.get(i).getNum();
  24. System.out.println(accountList.get(i).getNum());
  25. }
  26. System.out.println(sum);
  27. }
  28. }
  1. public class MulThreadTest1 implements Runnable{
  2. private List<Account> accountList;
  3. Account firstInteger;
  4. Account secondInteger;
  5. public MulThreadTest1(List accountList){
  6. this.accountList = accountList;
  7. }
  8. @Override
  9. public void run() {
  10. int first;
  11. int second;
  12. int num;
  13. for(int i = 0;i < 100;i ++) {
  14. first = new Random().nextInt(20);
  15. second = new Random().nextInt(20);
  16. if(first == second){
  17. if(second < 19){
  18. second += 1;
  19. }else {
  20. second = 1;
  21. }
  22. }
  23. firstInteger = accountList.get(first);
  24. synchronized (firstInteger) {
  25. num = new Random().nextInt(100);
  26. if (firstInteger.getNum() - num < 0) {
  27. i--;
  28. continue;
  29. }
  30. System.out.println(Thread.currentThread().getName() + ",操作前:" + "账户:" + first + "=" + accountList.get(first).getNum() + "账户:" + second + "=" + accountList.get(second).getNum());
  31. firstInteger.setNum(firstInteger.getNum() - num);
  32. accountList.set(first, firstInteger);
  33. secondInteger = accountList.get(second);
  34. synchronized (secondInteger) {
  35. secondInteger.setNum(secondInteger.getNum() + num);
  36. accountList.set(second, secondInteger);
  37. //accountList.set(first,accountList.get(first) - num);
  38. //accountList.set(second,accountList.get(second) + num);
  39. System.out.println(Thread.currentThread().getName() + ",操作:" + "账户:" + first + "=" + accountList.get(first).getNum() + "=>" + num + "=>" + "账户:" + second + "=" + accountList.get(second).getNum());
  40. }
  41. }
  42. }
  43. }
  44. }

部分结果如下:

  1. thread=4,操作前:账户:15=10037账户:16=10307
  2. thread=4,操作:账户:15=9972=>65=>账户:16=10372
  3. thread=4,操作前:账户:10=10012账户:16=10372
  4. thread=4,操作:账户:10=9958=>54=>账户:16=10426
  5. thread=4,操作前:账户:3=9854账户:19=9845
  6. thread=6,操作前:账户:14=9842账户:19=9998
  7. thread=0,操作前:账户:19=9845账户:14=9842
  8. thread=5,操作前:账户:2=9821账户:17=10387
  9. thread=5,操作:账户:2=9786=>35=>账户:17=10422
  10. thread=5,操作前:账户:0=9574账户:14=9822
  11. thread=8,操作前:账户:15=9972账户:16=10426
  12. thread=8,操作:账户:15=9962=>10=>账户:16=10436
  13. thread=7,操作前:账户:18=10367账户:19=9760
  14. thread=8,操作前:账户:17=10422账户:10=9958
  15. thread=8,操作:账户:17=10414=>8=>账户:10=9966
  16. thread=8,操作前:账户:1=10552账户:10=9966
  17. thread=8,操作:账户:1=10487=>65=>账户:10=10031
  18. 9528
  19. 10487
  20. 9786
  21. 9763
  22. 10109
  23. 9916
  24. 9162
  25. 9803
  26. 10237
  27. 10276
  28. 10031
  29. 10163
  30. 9987
  31. 9686
  32. 9822
  33. 9962
  34. 10436
  35. 10414
  36. 10359
  37. 9760
  38. 199687

使用jps和jstack即可看到线程状态

  1. Found one Java-level deadlock:
  2. =============================
  3. "thread=2":
  4. waiting to lock Monitor@0x00000000192b2598 (Object@0x00000000d5ea31b8, a com/xxx/testdemo/multhreadtest/Account),
  5. which is held by "thread=7"
  6. "thread=7":
  7. waiting to lock Monitor@0x00000000192b1038 (Object@0x00000000d5ea30a8, a com/xxx/testdemo/multhreadtest/Account),
  8. which is held by "thread=9"
  9. "thread=9":
  10. waiting to lock Monitor@0x00000000192b3cf8 (Object@0x00000000d5ea31a0, a com/xxx/testdemo/multhreadtest/Account),
  11. which is held by "thread=2"
  12. Found a total of 1 deadlock.
  13. Thread 1: (state = BLOCKED)
  14. Thread 26: (state = BLOCKED)
  15. - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
  16. - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
  17. Thread 25: (state = BLOCKED)
  18. - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=91, line=41 (Interpreted frame)
  19. - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
  20. Thread 24: (state = BLOCKED)
  21. - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
  22. - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
  23. Thread 23: (state = BLOCKED)
  24. - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=91, line=41 (Interpreted frame)
  25. - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
  26. Thread 22: (state = BLOCKED)
  27. - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=91, line=41 (Interpreted frame)
  28. - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
  29. Thread 21: (state = BLOCKED)
  30. - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
  31. - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
  32. Thread 20: (state = BLOCKED)
  33. - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
  34. - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
  35. Thread 19: (state = BLOCKED)
  36. - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
  37. - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
  38. Thread 18: (state = BLOCKED)
  39. - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
  40. - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
  41. Thread 17: (state = BLOCKED)
  42. - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=91, line=41 (Interpreted frame)
  43. - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
  44. Thread 11: (state = IN_NATIVE)
  45. - java.net.SocketInputStream.socketRead0(java.io.FileDescriptor, byte[], int, int, int) @bci=0 (Interpreted frame)
  46. - java.net.SocketInputStream.socketRead(java.io.FileDescriptor, byte[], int, int, int) @bci=8, line=116 (Interpreted frame)
  47. - java.net.SocketInputStream.read(byte[], int, int, int) @bci=117, line=171 (Interpreted frame)
  48. - java.net.SocketInputStream.read(byte[], int, int) @bci=11, line=141 (Interpreted frame)
  49. - sun.nio.cs.StreamDecoder.readBytes() @bci=135, line=284 (Interpreted frame)
  50. - sun.nio.cs.StreamDecoder.implRead(char[], int, int) @bci=112, line=326 (Interpreted frame)
  51. - sun.nio.cs.StreamDecoder.read(char[], int, int) @bci=180, line=178 (Interpreted frame)
  52. - java.io.InputStreamReader.read(char[], int, int) @bci=7, line=184 (Interpreted frame)
  53. - java.io.BufferedReader.fill() @bci=145, line=161 (Interpreted frame)
  54. - java.io.BufferedReader.readLine(boolean) @bci=44, line=324 (Interpreted frame)
  55. - java.io.BufferedReader.readLine() @bci=2, line=389 (Interpreted frame)
  56. - com.intellij.rt.execution.application.AppMainV2$1.run() @bci=36, line=47 (Interpreted frame)
  57. Thread 10: (state = BLOCKED)
  58. Thread 9: (state = BLOCKED)
  59. Thread 8: (state = BLOCKED)
  60. - java.lang.Object.wait(long) @bci=0 (Interpreted frame)
  61. - java.lang.ref.ReferenceQueue.remove(long) @bci=59, line=143 (Interpreted frame)
  62. - java.lang.ref.ReferenceQueue.remove() @bci=2, line=164 (Interpreted frame)
  63. - java.lang.ref.Finalizer$FinalizerThread.run() @bci=36, line=209 (Interpreted frame)
  64. Thread 7: (state = BLOCKED)
  65. - java.lang.Object.wait(long) @bci=0 (Interpreted frame)
  66. - java.lang.Object.wait() @bci=2, line=502 (Interpreted frame)
  67. - java.lang.ref.Reference.tryHandlePending(boolean) @bci=54, line=191 (Interpreted frame)
  68. - java.lang.ref.Reference$ReferenceHandler.run() @bci=1, line=153 (Interpreted frame)

发现已经deadlock了 

修改如下:

  1. public class MulThreadTest1 implements Runnable{
  2. private List<Account> accountList;
  3. Account firstInteger;
  4. Account secondInteger;
  5. volatile int flag1 = 0;
  6. volatile int flag2 = 0;
  7. public MulThreadTest1(List accountList){
  8. this.accountList = accountList;
  9. }
  10. @Override
  11. public void run() {
  12. int first;
  13. int second;
  14. int num;
  15. for(int i = 0;i < 100;i ++) {
  16. first = new Random().nextInt(20);
  17. second = new Random().nextInt(20);
  18. if(first == second){
  19. if(second < 19){
  20. second += 1;
  21. }else {
  22. second = 1;
  23. }
  24. }
  25. firstInteger = accountList.get(first);
  26. if(flag1 == 0) {
  27. flag1 = 1;
  28. synchronized (firstInteger) {
  29. num = new Random().nextInt(100);
  30. if (firstInteger.getNum() - num < 0) {
  31. i--;
  32. continue;
  33. }
  34. System.out.println(Thread.currentThread().getName() + ",操作前:" + "账户:" + first + "=" + accountList.get(first).getNum() + "账户:" + second + "=" + accountList.get(second).getNum());
  35. firstInteger.setNum(firstInteger.getNum() - num);
  36. accountList.set(first, firstInteger);
  37. }
  38. flag1 = 0;
  39. secondInteger = accountList.get(second);
  40. if(flag2 == 0) {
  41. flag2 = 1;
  42. synchronized (secondInteger) {
  43. secondInteger.setNum(secondInteger.getNum() + num);
  44. accountList.set(second, secondInteger);
  45. //accountList.set(first,accountList.get(first) - num);
  46. //accountList.set(second,accountList.get(second) + num);
  47. System.out.println(Thread.currentThread().getName() + ",操作:" + "账户:" + first + "=" + accountList.get(first).getNum() + "=>" + num + "=>" + "账户:" + second + "=" + accountList.get(second).getNum());
  48. }
  49. }
  50. }
  51. flag2 = 0;
  52. }
  53. }
  54. }

输出结果发现最终所有账户金额总额不是200000。

以上代码存在几个问题:

问题一:假如线程1对该对象进行操作时,从List中获取账户对象后,线程2也会从List中获取账户对象,然后当线程1抢到锁之前,线程2把线程1中的账户对象覆盖了,这样操作就有问题了。

问题二:假如线程1抢到了锁,并对账户1进行了操作,即将对账户2进行操作,此时发现flag2为1,那么此时线程1就会结束本次操作,这样线程1只对账户1进行了金额扣除,并没有回退操作,总金额就减少了。

问题三:设置两个flag值并不能防止死锁。当线程1锁了账户1,线程2锁了账户2,假设线程1和线程2同时制行了if(flag1==0),那么此时flag1并没有起到作用,之后线程1和线程2再次同时执行了if(flag2==0),线程1去对账户2操作,线程2去对账户1操作,发现都是锁着的,这就导致了死锁。

那么应该如何来防止以上死锁的情况呢?

只要打破造成死锁的条件即可,以上方案造成死锁的原因是同一个线程同时去抢占两个锁,这样很容易死锁,所以只要保证同一个线程同一个时刻只能获取一个锁这样就能避免死锁。

  1. public class MulThreadTest3 implements Runnable{
  2. private List<Account> accountList;
  3. //Account firstInteger;
  4. //Account secondInteger;
  5. public MulThreadTest3(List accountList){
  6. this.accountList = accountList;
  7. }
  8. @Override
  9. public void run() {
  10. int first;
  11. int second;
  12. int num;
  13. for(int i = 0;i < 100;i ++) {
  14. first = new Random().nextInt(20);
  15. second = new Random().nextInt(20);
  16. if(first == second){
  17. if(second < 19){
  18. second += 1;
  19. }else {
  20. second = 1;
  21. }
  22. }
  23. //System.out.println(Thread.currentThread().getStackTrace()+Thread.currentThread().getName());
  24. Account firstInteger = accountList.get(first);
  25. synchronized (firstInteger) {
  26. num = new Random().nextInt(100);
  27. if (firstInteger.getNum() - num < 0) {
  28. i--;
  29. continue;
  30. }
  31. System.out.println(Thread.currentThread().getStackTrace()[0].getMethodName()+Thread.currentThread().getName() + ",操作前:" + "账户:" + first + "=" + accountList.get(first).getNum() + "账户:" + second + "=" + accountList.get(second).getNum());
  32. firstInteger.setNum(firstInteger.getNum() - num);
  33. firstInteger.setThreadName(Thread.currentThread().getName());
  34. accountList.set(first, firstInteger);
  35. }
  36. Account secondInteger = accountList.get(second);
  37. synchronized (secondInteger) {
  38. secondInteger.setNum(secondInteger.getNum() + num);
  39. firstInteger.setThreadName(Thread.currentThread().getName());
  40. accountList.set(second, secondInteger);
  41. //accountList.set(first,accountList.get(first) - num);
  42. //accountList.set(second,accountList.get(second) + num);
  43. System.out.println(Thread.currentThread().getStackTrace()[0].getMethodName()+Thread.currentThread().getName() + ",操作:" + "账户:" + first + "=" + accountList.get(first).getNum() + "=>" + num + "=>" + "账户:" + second + "=" + accountList.get(second).getNum());
  44. }
  45. }
  46. }
  47. }

部分运行结果如下

  1. getStackTracethread=0,操作前:账户:5=11323账户:19=9676
  2. getStackTracethread=1,操作:账户:16=10083=>10=>账户:10=9543
  3. getStackTracethread=0,操作:账户:5=11299=>24=>账户:19=9700
  4. getStackTracethread=1,操作前:账户:11=10993账户:9=10525
  5. getStackTracethread=0,操作前:账户:18=9344账户:1=9939
  6. getStackTracethread=1,操作:账户:11=10983=>10=>账户:9=10535
  7. getStackTracethread=0,操作:账户:18=9324=>20=>账户:1=9959
  8. getStackTracethread=1,操作前:账户:10=9543账户:19=9700
  9. getStackTracethread=0,操作前:账户:18=9324账户:10=9543
  10. getStackTracethread=1,操作:账户:10=9522=>21=>账户:19=9721
  11. getStackTracethread=0,操作:账户:18=9307=>17=>账户:10=9539
  12. getStackTracethread=1,操作前:账户:19=9721账户:15=10155
  13. getStackTracethread=1,操作:账户:19=9689=>32=>账户:15=10187
  14. getStackTracethread=1,操作前:账户:3=10113账户:5=11299
  15. getStackTracethread=1,操作:账户:3=10097=>16=>账户:5=11315
  16. getStackTracethread=1,操作前:账户:13=9419账户:11=10983
  17. getStackTracethread=1,操作:账户:13=9398=>21=>账户:11=11004
  18. getStackTracethread=1,操作前:账户:5=11315账户:3=10097
  19. getStackTracethread=1,操作:账户:5=11251=>64=>账户:3=10161
  20. 9280
  21. 9959
  22. 10004
  23. 10161
  24. 10641
  25. 11251
  26. 10040
  27. 8710
  28. 9800
  29. 10535
  30. 9539
  31. 11004
  32. 10635
  33. 9398
  34. 9814
  35. 10187
  36. 10083
  37. 9963
  38. 9307
  39. 9689
  40. 200000

总结

本题考察了多线程,以及多线程场景中锁的应用,如何避免死锁是关键,方案一虽然能很快解决本地,但是应该不是最理想的方案。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/574586
推荐阅读
相关标签
  

闽ICP备14008679号