前言
某公司一个面试题:
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());
}
}
}
}
}
然后运行后发现死锁了,再进行修改
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抢到锁之前,把线程1中的账户对象覆盖了,这样操作就有问题了。