WangJiQing

导航

买票&转账问题

买票&转账问题

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

posted on 2022-12-25 12:14  如梦幻泡影  阅读(24)  评论(0编辑  收藏  举报