买票&转账问题
买票&转账问题
1、买票问题
现象
模拟2200人来买票,总票数为2000张票。出现线程安全问题:卖票超过总票数
public class ExerciseSell {
public static void main(String[] args) {
//卖票窗口,有2000张票
TicketWindow window = new TicketWindow(2000);
//线程集合、模拟来买票的人
List<Thread> threads = new ArrayList<>();
//卖票集合,卖了多少张票
List<Integer> ticketAmount = new Vector<>();
// ArrayList<Integer> ticketAmount = new ArrayList<>();
for (int i = 0; i < 2200; i++) {//模拟2200个人来买票
Thread t = new Thread(() -> {
int amount = window.sell(randomAmount());//卖票
ticketAmount.add(amount);//卖了多少张票,添加到卖票集合
});
threads.add(t);//添加到线程集合
t.start();
}
threads.forEach(t -> {
try {
t.join();//等待所有人买完票
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("卖出去了多少张票:" + ticketAmount.stream().mapToInt(c -> c).sum());
System.out.println("剩余票数:" + window.getCount());
}
//随机1~5张票
public static int randomAmount() {
return new Random().nextInt(5) + 1;
}
}
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public int sell(int amount) {
if (count >= amount) {//余票数量大于需要卖出的数量
count -= amount;
return amount;
} else {
return 0;
}
}
}
结果
卖出去了多少张票:2025
剩余票数:0
解决方法
在TicketWindow类的sell方法中,锁住类的实例对象,保证了代码块的原子性
public synchronized int sell(int amount) {
if (count >= amount) {//余票数量大于需要卖出的数量
count -= amount;
return amount;
} else {
return 0;
}
}
//或者这样写也可以
public int sell(int amount) {
synchronized (this) {
if (count >= amount) {//余票数量大于需要卖出的数量
count -= amount;
return amount;
} else {
return 0;
}
}
}
结果
卖出去了多少张票:2000
剩余票数:0
2、转账问题
现象
两个人各有1000元,互相向对方转账2000次,每次转账1~100元。出现线程不安全,最终两个人的总余额不为2000
@Slf4j
public class ExerciseTransfer {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 2000; i++) {
a.transfer(b, randomMoney());
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 2000; i++) {
b.transfer(a, randomMoney());
}
}, "t2");
t1.start();
t2.start();
t1.join();//main线程等待t1线程执行完
t2.join();//main线程等待t2线程执行完
log.debug("查看2000次转账后的总金额:{}", a.getMoney() + b.getMoney());
}
static Random r = new Random();
private static int randomMoney() {
return r.nextInt(100) + 1;
}
}
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void transfer(Account target, int amount) {
if (this.money >= amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
结果
10:21:20.823 [main] DEBUG cn.wjq.four.转账.ExerciseTransfer - 查看2000次转账后的总金额:5
解决方法
在Account类中的transfer方法中,锁住类对象,保证了代码块的原子性
public void transfer(Account target, int amount) {
synchronized (Account.class){//锁住类对象
if (this.money >= amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
结果
10:32:11.971 [main] DEBUG cn.wjq.four.转账.ExerciseTransfer - 查看2000次转账后的总金额:7966
锁住类的实例对象?
锁住类的实例对象,行不行?不行,因为方法中传递参数,有Account的其他对象,无法保证target对象的余额,所以需要锁住当前对象(this)和target,Account.class对当前类的所有实例对象所共享
public void transfer(Account target, int amount) {
synchronized (this){//锁住类对象
if (this.money >= amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
结果
10:32:45.684 [main] DEBUG cn.wjq.four.转账.ExerciseTransfer - 查看2000次转账后的总金额:13
本文来自博客园,作者:如梦幻泡影,转载请注明原文链接:https://www.cnblogs.com/WangJiQing/p/17003857.html