赞
踩
某公司一个面试题:
1.有二十个账户,每个账户初始余额10000元。
2.有十个转账线程,对二十个账户中的两个随机选取账户进行转账,转账额度100以内正整数随机数。
3.每个线程执行100次转账操作。
4.最后请打印出二十个账户的余额。
正好很久没有做这类型题了,拿来练练手,结果碰到了一些问题。
方案一:
首先描述下思路,首先用一个List数组存20个账户,然后对每个账户赋初值10000,在新建10个转账线程,对List数组中20个账户进行并发操作,每个线程分别获取两个账户进行随机额度转账。那么最简单的方式就是对List数组进行加锁就可以了。代码实现如下:
- public class test {
- static int num;
- public static void main(String[] args) {
- List<Integer> accountList = new ArrayList<Integer>();
- for (int i = 0;i < 20;i++){
- accountList.add(10000);
- }
- System.out.println(num);
-
- MulThreadTest mulThreadTest = new MulThreadTest(accountList);
- ThreadGroup threadGroup =new ThreadGroup("ThreadGroup1");
- for (int i = 0;i < 10;i++){
- Thread thread = new Thread(mulThreadTest);
- thread.setName("thread="+i);
- thread.start();
- }
- try{
- Thread.sleep(5000);
- }catch(Exception e){
- e.printStackTrace();
- }
- int sum = 0;
- for (int i = 0;i < 20;i++){
- sum = sum + accountList.get(i);
- System.out.println(accountList.get(i));
- }
- System.out.println(sum);
- }
- }
- public class MulThreadTest implements Runnable{
- private List<Integer> accountList;
- private int first;
- private int second;
- private int num;
-
- public MulThreadTest(List accountList){
- this.accountList = accountList;
- }
- @Override
- public void run() {
- synchronized (accountList){
- for(int i = 0;i < 100;i ++) {
- first = new Random().nextInt(20);
- second = new Random().nextInt(20);
- if(first == second){
- if(second < 19){
- second += 1;
- }else {
- second = 1;
- }
-
- }
- num = new Random().nextInt(100);
- if(accountList.get(first) - num < 0){
- i --;
- continue;
- }
- System.out.println(Thread.currentThread().getName()+",操作前:"+"账户:"+first+"="+accountList.get(first)+"账户:"+second+"="+accountList.get(second));
- accountList.set(first,accountList.get(first) - num);
- accountList.set(second,accountList.get(second) + num);
- System.out.println(Thread.currentThread().getName()+",操作:"+"账户:"+first+"="+accountList.get(first)+"=>"+num+"=>"+"账户:"+second+"="+accountList.get(second));
- }
- }
-
- }
- }
以下是20个账户的金额以及总金额
- 10114
- 10046
- 10268
- 10676
- 10250
- 10344
- 9899
- 10120
- 9629
- 10145
- 9211
- 9761
- 9903
- 10187
- 9374
- 10508
- 10046
- 10005
- 9529
- 9985
- 200000
以上可以解决这个转账并发问题,但是锁的粒度还是有点大了,在一个线程操作List时其它线程是没法操作的,那么如何减小锁的粒度,实现更高的效率呢?
方案二:
在方案一的基础上,把锁的对象修改为List中的某个账户,这样当其中一个线程在操作某个账户时,其它线程就仅仅无法操作该账户,影响范围能减小很多。代码实现如下:
- public class test {
- static int num;
- public static void main(String[] args) {
- List<Account> accountList = new ArrayList<Account>();
- for (int i = 0;i < 20;i++){
- accountList.add(new Account(10000));
- }
- System.out.println(num);
-
- MulThreadTest1 mulThreadTest = new MulThreadTest1(accountList);
- ThreadGroup threadGroup =new ThreadGroup("ThreadGroup1");
- for (int i = 0;i < 10;i++){
- Thread thread = new Thread(mulThreadTest);
- thread.setName("thread="+i);
- thread.start();
- }
- try{
- Thread.sleep(5000);
- }catch(Exception e){
- e.printStackTrace();
- }
- int sum = 0;
- for (int i = 0;i < 20;i++){
- sum = sum + accountList.get(i).getNum();
- System.out.println(accountList.get(i).getNum());
- }
- System.out.println(sum);
- }
- }
- public class MulThreadTest1 implements Runnable{
- private List<Account> accountList;
- Account firstInteger;
- Account secondInteger;
-
-
- public MulThreadTest1(List accountList){
- this.accountList = accountList;
- }
- @Override
- public void run() {
- int first;
- int second;
- int num;
-
- for(int i = 0;i < 100;i ++) {
- first = new Random().nextInt(20);
- second = new Random().nextInt(20);
- if(first == second){
- if(second < 19){
- second += 1;
- }else {
- second = 1;
- }
-
- }
- firstInteger = accountList.get(first);
-
- synchronized (firstInteger) {
-
- num = new Random().nextInt(100);
- if (firstInteger.getNum() - num < 0) {
- i--;
- continue;
- }
- System.out.println(Thread.currentThread().getName() + ",操作前:" + "账户:" + first + "=" + accountList.get(first).getNum() + "账户:" + second + "=" + accountList.get(second).getNum());
-
- firstInteger.setNum(firstInteger.getNum() - num);
-
- accountList.set(first, firstInteger);
-
- secondInteger = accountList.get(second);
-
- synchronized (secondInteger) {
- secondInteger.setNum(secondInteger.getNum() + num);
- accountList.set(second, secondInteger);
- //accountList.set(first,accountList.get(first) - num);
- //accountList.set(second,accountList.get(second) + num);
- System.out.println(Thread.currentThread().getName() + ",操作:" + "账户:" + first + "=" + accountList.get(first).getNum() + "=>" + num + "=>" + "账户:" + second + "=" + accountList.get(second).getNum());
- }
-
- }
-
-
-
- }
-
- }
- }
部分结果如下:
- thread=4,操作前:账户:15=10037账户:16=10307
- thread=4,操作:账户:15=9972=>65=>账户:16=10372
- thread=4,操作前:账户:10=10012账户:16=10372
- thread=4,操作:账户:10=9958=>54=>账户:16=10426
- thread=4,操作前:账户:3=9854账户:19=9845
- thread=6,操作前:账户:14=9842账户:19=9998
- thread=0,操作前:账户:19=9845账户:14=9842
- thread=5,操作前:账户:2=9821账户:17=10387
- thread=5,操作:账户:2=9786=>35=>账户:17=10422
- thread=5,操作前:账户:0=9574账户:14=9822
- thread=8,操作前:账户:15=9972账户:16=10426
- thread=8,操作:账户:15=9962=>10=>账户:16=10436
- thread=7,操作前:账户:18=10367账户:19=9760
- thread=8,操作前:账户:17=10422账户:10=9958
- thread=8,操作:账户:17=10414=>8=>账户:10=9966
- thread=8,操作前:账户:1=10552账户:10=9966
- thread=8,操作:账户:1=10487=>65=>账户:10=10031
- 9528
- 10487
- 9786
- 9763
- 10109
- 9916
- 9162
- 9803
- 10237
- 10276
- 10031
- 10163
- 9987
- 9686
- 9822
- 9962
- 10436
- 10414
- 10359
- 9760
- 199687
使用jps和jstack即可看到线程状态
- Found one Java-level deadlock:
- =============================
-
- "thread=2":
- waiting to lock Monitor@0x00000000192b2598 (Object@0x00000000d5ea31b8, a com/xxx/testdemo/multhreadtest/Account),
- which is held by "thread=7"
- "thread=7":
- waiting to lock Monitor@0x00000000192b1038 (Object@0x00000000d5ea30a8, a com/xxx/testdemo/multhreadtest/Account),
- which is held by "thread=9"
- "thread=9":
- waiting to lock Monitor@0x00000000192b3cf8 (Object@0x00000000d5ea31a0, a com/xxx/testdemo/multhreadtest/Account),
- which is held by "thread=2"
-
- Found a total of 1 deadlock.
-
- Thread 1: (state = BLOCKED)
-
-
- Thread 26: (state = BLOCKED)
- - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
- - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
-
-
- Thread 25: (state = BLOCKED)
- - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=91, line=41 (Interpreted frame)
- - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
-
-
- Thread 24: (state = BLOCKED)
- - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
- - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
-
-
- Thread 23: (state = BLOCKED)
- - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=91, line=41 (Interpreted frame)
- - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
-
-
- Thread 22: (state = BLOCKED)
- - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=91, line=41 (Interpreted frame)
- - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
-
-
- Thread 21: (state = BLOCKED)
- - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
- - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
-
-
- Thread 20: (state = BLOCKED)
- - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
- - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
-
-
- Thread 19: (state = BLOCKED)
- - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
- - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
-
-
- Thread 18: (state = BLOCKED)
- - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=270, line=56 (Interpreted frame)
- - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
-
-
- Thread 17: (state = BLOCKED)
- - com.rain.testdemo.multhreadtest.MulThreadTest1.run() @bci=91, line=41 (Interpreted frame)
- - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
-
-
- Thread 11: (state = IN_NATIVE)
- - java.net.SocketInputStream.socketRead0(java.io.FileDescriptor, byte[], int, int, int) @bci=0 (Interpreted frame)
- - java.net.SocketInputStream.socketRead(java.io.FileDescriptor, byte[], int, int, int) @bci=8, line=116 (Interpreted frame)
- - java.net.SocketInputStream.read(byte[], int, int, int) @bci=117, line=171 (Interpreted frame)
- - java.net.SocketInputStream.read(byte[], int, int) @bci=11, line=141 (Interpreted frame)
- - sun.nio.cs.StreamDecoder.readBytes() @bci=135, line=284 (Interpreted frame)
- - sun.nio.cs.StreamDecoder.implRead(char[], int, int) @bci=112, line=326 (Interpreted frame)
- - sun.nio.cs.StreamDecoder.read(char[], int, int) @bci=180, line=178 (Interpreted frame)
- - java.io.InputStreamReader.read(char[], int, int) @bci=7, line=184 (Interpreted frame)
- - java.io.BufferedReader.fill() @bci=145, line=161 (Interpreted frame)
- - java.io.BufferedReader.readLine(boolean) @bci=44, line=324 (Interpreted frame)
- - java.io.BufferedReader.readLine() @bci=2, line=389 (Interpreted frame)
- - com.intellij.rt.execution.application.AppMainV2$1.run() @bci=36, line=47 (Interpreted frame)
-
-
- Thread 10: (state = BLOCKED)
-
-
- Thread 9: (state = BLOCKED)
-
-
- Thread 8: (state = BLOCKED)
- - java.lang.Object.wait(long) @bci=0 (Interpreted frame)
- - java.lang.ref.ReferenceQueue.remove(long) @bci=59, line=143 (Interpreted frame)
- - java.lang.ref.ReferenceQueue.remove() @bci=2, line=164 (Interpreted frame)
- - java.lang.ref.Finalizer$FinalizerThread.run() @bci=36, line=209 (Interpreted frame)
-
-
- Thread 7: (state = BLOCKED)
- - java.lang.Object.wait(long) @bci=0 (Interpreted frame)
- - java.lang.Object.wait() @bci=2, line=502 (Interpreted frame)
- - java.lang.ref.Reference.tryHandlePending(boolean) @bci=54, line=191 (Interpreted frame)
- - java.lang.ref.Reference$ReferenceHandler.run() @bci=1, line=153 (Interpreted frame)
发现已经deadlock了
修改如下:
- public class MulThreadTest1 implements Runnable{
- private List<Account> accountList;
- Account firstInteger;
- Account secondInteger;
- volatile int flag1 = 0;
- volatile int flag2 = 0;
-
-
- public MulThreadTest1(List accountList){
- this.accountList = accountList;
- }
- @Override
- public void run() {
- int first;
- int second;
- int num;
-
- for(int i = 0;i < 100;i ++) {
- first = new Random().nextInt(20);
- second = new Random().nextInt(20);
- if(first == second){
- if(second < 19){
- second += 1;
- }else {
- second = 1;
- }
-
- }
- firstInteger = accountList.get(first);
- if(flag1 == 0) {
- flag1 = 1;
- synchronized (firstInteger) {
-
- num = new Random().nextInt(100);
- if (firstInteger.getNum() - num < 0) {
- i--;
- continue;
- }
- System.out.println(Thread.currentThread().getName() + ",操作前:" + "账户:" + first + "=" + accountList.get(first).getNum() + "账户:" + second + "=" + accountList.get(second).getNum());
-
- firstInteger.setNum(firstInteger.getNum() - num);
-
- accountList.set(first, firstInteger);
-
- }
- flag1 = 0;
- secondInteger = accountList.get(second);
- if(flag2 == 0) {
- flag2 = 1;
- synchronized (secondInteger) {
- secondInteger.setNum(secondInteger.getNum() + num);
- accountList.set(second, secondInteger);
- //accountList.set(first,accountList.get(first) - num);
- //accountList.set(second,accountList.get(second) + num);
- System.out.println(Thread.currentThread().getName() + ",操作:" + "账户:" + first + "=" + accountList.get(first).getNum() + "=>" + num + "=>" + "账户:" + second + "=" + accountList.get(second).getNum());
- }
- }
- }
-
- flag2 = 0;
-
- }
-
- }
- }
输出结果发现最终所有账户金额总额不是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操作,发现都是锁着的,这就导致了死锁。
那么应该如何来防止以上死锁的情况呢?
只要打破造成死锁的条件即可,以上方案造成死锁的原因是同一个线程同时去抢占两个锁,这样很容易死锁,所以只要保证同一个线程同一个时刻只能获取一个锁这样就能避免死锁。
- public class MulThreadTest3 implements Runnable{
- private List<Account> accountList;
- //Account firstInteger;
- //Account secondInteger;
-
- public MulThreadTest3(List accountList){
- this.accountList = accountList;
- }
- @Override
- public void run() {
- int first;
- int second;
- int num;
-
- for(int i = 0;i < 100;i ++) {
- first = new Random().nextInt(20);
- second = new Random().nextInt(20);
- if(first == second){
- if(second < 19){
- second += 1;
- }else {
- second = 1;
- }
- }
- //System.out.println(Thread.currentThread().getStackTrace()+Thread.currentThread().getName());
- Account firstInteger = accountList.get(first);
- synchronized (firstInteger) {
-
- num = new Random().nextInt(100);
- if (firstInteger.getNum() - num < 0) {
- i--;
- continue;
- }
- System.out.println(Thread.currentThread().getStackTrace()[0].getMethodName()+Thread.currentThread().getName() + ",操作前:" + "账户:" + first + "=" + accountList.get(first).getNum() + "账户:" + second + "=" + accountList.get(second).getNum());
-
- firstInteger.setNum(firstInteger.getNum() - num);
- firstInteger.setThreadName(Thread.currentThread().getName());
- accountList.set(first, firstInteger);
- }
-
- Account secondInteger = accountList.get(second);
- synchronized (secondInteger) {
- secondInteger.setNum(secondInteger.getNum() + num);
- firstInteger.setThreadName(Thread.currentThread().getName());
- accountList.set(second, secondInteger);
- //accountList.set(first,accountList.get(first) - num);
- //accountList.set(second,accountList.get(second) + num);
- System.out.println(Thread.currentThread().getStackTrace()[0].getMethodName()+Thread.currentThread().getName() + ",操作:" + "账户:" + first + "=" + accountList.get(first).getNum() + "=>" + num + "=>" + "账户:" + second + "=" + accountList.get(second).getNum());
- }
-
- }
-
- }
- }
部分运行结果如下
- getStackTracethread=0,操作前:账户:5=11323账户:19=9676
- getStackTracethread=1,操作:账户:16=10083=>10=>账户:10=9543
- getStackTracethread=0,操作:账户:5=11299=>24=>账户:19=9700
- getStackTracethread=1,操作前:账户:11=10993账户:9=10525
- getStackTracethread=0,操作前:账户:18=9344账户:1=9939
- getStackTracethread=1,操作:账户:11=10983=>10=>账户:9=10535
- getStackTracethread=0,操作:账户:18=9324=>20=>账户:1=9959
- getStackTracethread=1,操作前:账户:10=9543账户:19=9700
- getStackTracethread=0,操作前:账户:18=9324账户:10=9543
- getStackTracethread=1,操作:账户:10=9522=>21=>账户:19=9721
- getStackTracethread=0,操作:账户:18=9307=>17=>账户:10=9539
- getStackTracethread=1,操作前:账户:19=9721账户:15=10155
- getStackTracethread=1,操作:账户:19=9689=>32=>账户:15=10187
- getStackTracethread=1,操作前:账户:3=10113账户:5=11299
- getStackTracethread=1,操作:账户:3=10097=>16=>账户:5=11315
- getStackTracethread=1,操作前:账户:13=9419账户:11=10983
- getStackTracethread=1,操作:账户:13=9398=>21=>账户:11=11004
- getStackTracethread=1,操作前:账户:5=11315账户:3=10097
- getStackTracethread=1,操作:账户:5=11251=>64=>账户:3=10161
- 9280
- 9959
- 10004
- 10161
- 10641
- 11251
- 10040
- 8710
- 9800
- 10535
- 9539
- 11004
- 10635
- 9398
- 9814
- 10187
- 10083
- 9963
- 9307
- 9689
- 200000
本题考察了多线程,以及多线程场景中锁的应用,如何避免死锁是关键,方案一虽然能很快解决本地,但是应该不是最理想的方案。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。