JUC------共享模型------示例练习

承接上篇

一、售票

注释中的解释是星火根据这两个代码做出的解释,感觉有点道理。

运行10次的测试脚本win(注意要在classes目录下运行): for /L %n in (1,1,10) do java -cp . com.wang.juc.sell_ticket.ExerciseSell

方式一:自己写的,虽然解决了线程安全问题,但是相当于没有用多线程

/**
 * 在第一个代码(ExerciseSell)中,我们创建了一个TicketWindow对象,并在每个线程中都对其进行了同步操作。
 * 也就是说,当一个线程正在执行sell方法时,其他线程必须等待该线程完成才能继续执行。这可以防止多个线程同时修改票的数量,
 * 从而避免了竞态条件。然而,这也意味着线程必须等待其他线程,可能会导致性能下降。
 */
public class ExerciseSell {
    public static void main(String[] args) {
        TicketWindow ticketWindow = new TicketWindow(2000);
        List<Thread> list = new ArrayList<>();
        // 用来存储买出去多少张票
        List<Integer> sellCount = new Vector<>();
        for (int i = 0; i < 2000; i++) {
            Thread t = new Thread(() -> {
                // 分析这里的竞态条件
                synchronized (ticketWindow){
                    int count = ticketWindow.sell(randomAmount());
                    sellCount.add(count);
                }

            });
            list.add(t);
            t.start();
        }
        list.forEach((t) -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 买出去的票求和
        System.out.println("买出去的票求和==>:"+ sellCount.stream().mapToInt(c -> c).sum());
        // 剩余票数
//        log.debug("剩余票数:{}", ticketWindow.getCount());
        System.out.println("剩余票数==>:"+ ticketWindow.getCount());
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~5
    public static int randomAmount() {
        return 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 (this.count >= amount) {
            this.count -= amount;
            return amount;
        } else {
            return 0;
        }
    }
}

方式二:视频中的答案

/**
 * 在第二个代码(ExerciseSell2)中,我们没有在每个线程中对TicketWindow对象进行同步操作,
 * 而是在TicketWindow类中的sell方法上添加了synchronized关键字。
 * 这意味着,无论何时,只有一个线程可以访问sell方法。
 * 这样,我们可以避免在多个线程同时访问sell方法时可能出现的竞态条件。
 */
@Slf4j
public class ExerciseSell2 {
    public static void main(String[] args) {
        TicketWindow2 ticketWindow = new TicketWindow2(2000);
        List<Thread> list = new ArrayList<>();
        // 用来存储买出去多少张票
        List<Integer> sellCount = new Vector<>();
        for (int i = 0; i < 2000; i++) {
            Thread t = new Thread(() -> {
                // 分析这里的竞态条件
                int count = ticketWindow.sell(randomAmount());
                sellCount.add(count);
            });
            list.add(t);
            t.start();
        }
        list.forEach((t) -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 买出去的票求和
        System.out.println("买出去的票求和==>:"+ sellCount.stream().mapToInt(c -> c).sum());
        // 剩余票数
//        log.debug("剩余票数:{}", ticketWindow.getCount());
        System.out.println("剩余票数==>:"+ ticketWindow.getCount());
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~5
    public static int randomAmount() {
        return random.nextInt(5) + 1;
    }
}

class TicketWindow2 {
    private int count;

    public TicketWindow2(int count) {
        this.count = count;
    }

    public int getCount() {
        return count;
    }

    public synchronized int sell(int amount) {
        if (this.count >= amount) {
            this.count -= amount;
            return amount;
        } else {
            return 0;
        }
    }
}

二、转账

原视频 https://www.bilibili.com/video/BV16J411h7Rd?p=74

问题代码:

@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 < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        // 查看转账2000次后的总金额
        log.debug("total:{}", (a.getMoney() + b.getMoney()));
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~100
    public static int randomAmount() {
        return random.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);
        }
    }
}

解决方案:
问题分析: 首先对于此代码先找出其临界区(出现线程安全的代码),显而易见的该临界区是Account 的transfer方法,找到临界区之后 再找线程的共享变量,其共享变量为money,但是其money有两个地方改变,一个是this.setMoney,,一个是target.setMoney ,this中money和target中的money不是一样对象,所以如果使用

    public synchronized void transfer(Account target, int amount) {
        if (this.money > amount) {
            this.setMoney(this.getMoney() - amount);
            target.setMoney(target.getMoney() + amount);
        }
    }
//相当于
    public  void transfer(Account target, int amount) {
        synchronized (this){
            if (this.money > amount) {
                this.setMoney(this.getMoney() - amount);
                target.setMoney(target.getMoney() + amount);
            } 
        }
    }

其只会锁住this实例,但target实例无法锁住,所以目前正确的修改方式为

    public void transfer(Account target, int amount) {
        synchronized (Account.class){
            if (this.money > amount) {
                this.setMoney(this.getMoney() - amount);
                target.setMoney(target.getMoney() + amount);
            }
        }
    }

使用synchronized (Account.class) 锁住其类对象在这个类的所有实例中,只有一个线程能够执行被synchronized(Account .class)包围的代码块,其他线程需要等待

当然,此种方案效率比较低,其只是在目前文章的基础上作的修改,后续还会优化。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值