(二)java多线程之synchronized

news/2024/6/19 21:14:28 标签: java

本人邮箱: <kco1989@qq.com>
欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kco
github: https://github.com/kco1989/kco
代码已经全部托管github有需要的同学自行下载

引言

现在,让我们来考虑一个问题,如果要让多个线程来访问同一份数据,会发生什么现象呢?比如12306的火车售票系统,比如银行的存取款系统等等.都可以会出现多线程访问同一个数据的情况.让我们先模拟写一个售票系统.

编码

  • 首先创建一个Ticket

    • 增加两个成员变量count-->表示剩余的票,buyedCount-->已经卖出的票,并提供getter方法

    • 增加一个buyTicket方法,用来模拟售票

java">public class Ticket {
    private static final int DEFAULT_TICKET_COUNT = 1000;
    private int count = DEFAULT_TICKET_COUNT; //票的总数
    private int buyedCount = 0;

    public boolean buyTicket(int count) throws InterruptedException {
            if (this.count - count < 0){
                Thread.sleep(10);
                return false;
            }else{
                this.count = this.count - count;
                Thread.sleep(1);
                this.buyedCount = this.buyedCount + count;
                return true;
            }
    }

    public int getCount() {
        return count;
    }

    public int getBuyedCount() {
        return buyedCount;
    }

    public int getAllCount(){
        return count + buyedCount;
    }
}
  • 之后创建一个模拟售票的类TicketRunnable,该类的构造器接收一个Ticket

java">public static class TicketRunnable implements Runnable{
    private Ticket ticket;
    private Random random;
    public TicketRunnable(Ticket ticket) {
        this.ticket = ticket;
        random = new Random();
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i ++){
            try {
                int count =  random.nextInt(10) + 1;
                boolean success = ticket.buyTicket(count);
                System.out.println(String.format("%s打算买%d张票,买票%s了,还剩下%d张票,总共卖掉%d张票, 总票数%d",
                        Thread.currentThread().getName(), count, success ? "成功" : "失败",
                        ticket.getCount(),ticket.getBuyedCount(),ticket.getAllCount()));
                if (!success){
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}
  • 最后创建一个main模拟20个售票点同时售票

java">public static void main(String[] args) throws InterruptedException {
    List<Thread> threads = new ArrayList<>();
    Ticket ticket = new Ticket();
    for (int i = 0; i < 20; i ++){
        threads.add(new Thread(new TicketRunnable(ticket)));
    }

    for (Thread thread : threads){
        thread.start();
    }
}
  • 截取某一次的部分运行结果:

Thread-1打算买2张票,买票成功了,还剩下441张票,总共卖掉558张票, 总票数999
Thread-8打算买1张票,买票成功了,还剩下441张票,总共卖掉552张票, 总票数993
Thread-6打算买1张票,买票成功了,还剩下434张票,总共卖掉559张票, 总票数993
Thread-14打算买7张票,买票成功了,还剩下431张票,总共卖掉566张票, 总票数997
Thread-6打算买3张票,买票成功了,还剩下431张票,总共卖掉569张票, 总票数1000

问题

发现程序运行确实有问题

java提供了关键字synchronized可以保证数据同步,在TicketbuyTicketgetter方法前加上synchronized,之后在运行一下程序,

Thread-13打算买4张票,买票成功了,还剩下457张票,总共卖掉543张票, 总票数1000
Thread-0打算买2张票,买票成功了,还剩下479张票,总共卖掉524张票, 总票数1000
Thread-6打算买4张票,买票成功了,还剩下444张票,总共卖掉556张票, 总票数1000
Thread-0打算买9张票,买票成功了,还剩下444张票,总共卖掉556张票, 总票数1000
Thread-6打算买2张票,买票成功了,还剩下442张票,总共卖掉558张票, 总票数1000

发现程序没有问题了

getter方法上加synchronized是因为获取的变量也是公共的数据

解决办法

synchronized的另外一种用法是在方法体内使用.在上述的例子中,在方法前加synchronized其实等效于synchronized(this){方法体},因为在上述的例子中公共的数据就是Ticket ticket = new Ticket();这个变量,在Ticket类中就相当与变量this

还有不使用synchronized(this){方法体}中的this也可以替换为另外一个公共的变量,如在Ticket类中定义个成员变量Object o = new Object();,然后使用synchronized(o){方法体}也可以保证数据同步.

打个比喻,比如现在有很多人都想进入某一个房间的卧室(至于想干嘛,大家自己脑补),synchronized(对象)中的对象就是一扇门,

synchronized就是给这扇门加锁.那么不管这扇门是房间最外的大门,或者是卧室的门.只要所有人对这同一个门在同一个时间点仅仅有且只有一个能开门或者关门.那么就能保证进入卧室的人只有一个.

这里举个反例,比如进入卧室有两种渠道,一种是进前门,一种是进后门.(为什么卧室有前后门,肯定是有特殊用户了,哈哈...),那么有些人对前门加锁,另外一些人对后门加锁.这样就不能保证进入卧室的人只有一个了.(悲剧说不定就这样发生了)

在类的静态方法加synchronized,等效于synchronized(类.class){方法体}

另外,我们也在在不修改Ticket的基础上来保证售票数据的同步,只需要将TicketRunnable.run方法改为

java">public void run() {
    for (int i = 0; i < 5; i ++){
        synchronized (ticket){
            try {
                int count =  random.nextInt(10) + 1;
                boolean success = ticket.buyTicket(count);
                System.out.println(String.format("%s打算买%d张票,买票%s了,还剩下%d张票,总共卖掉%d张票, 总票数%d",
                        Thread.currentThread().getName(), count, success ? "成功" : "失败",
                        ticket.getCount(),ticket.getBuyedCount(),ticket.getAllCount()));
                if (!success){
                    break;
                }
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这样也能保证售票正常,那在这里能不能把synchronized (ticket){...}改为synchronized (random){...}呢?不能,因为random不是同一个对象,即各个线程只对自己的门加锁,不能保证是对同一个门加锁.

打赏

如果觉得我的文章写的还过得去的话,有钱就捧个钱场,没钱给我捧个人场(帮我点赞或推荐一下)
微信打赏
支付宝打赏


http://www.niftyadmin.cn/n/963430.html

相关文章

提高ASP.NET首页性能的十大方法

1.采用 HTTP Module 控制页面的生命周期。 2.自定义Response.Filter得到输出流stream生成动态页面的静态内容(磁盘缓存)。 3.页面GZIP压缩。 4.OutputCache 编程方式输出页面缓存。 5.删除页面空白字符串。&#xff08;类似Google&#xff09; 6.完全删除ViewState。 7.删…

LabVIEW访问注册表和license的信息

LabVIEW访问注册表和license的信息 如何在对一个VI的编程中访问LabVIEW注册表和license的信息&#xff1f;需要在打开LabVIEW时显示以下信息-RegisteredOrganization,Registered Owner,和序列号。 解答: LabVIEW将注册信息存储在Windows注册表下的HKEY_LOCAL_MACHINE\Softwar…

剪枝计算机,α-β剪枝 - 电脑黑白棋 - 黑白棋天地

α-β剪枝算法前面介绍的基本搜索算法&#xff0c;在实际应用是是十分费时的&#xff0c;因为它需要考虑所有可能的棋步。有研究表明&#xff0c;在黑白棋的中盘阶段&#xff0c;平均每个局面大约有10步棋可供选择[1]。如果程序前瞻10步(搜索深度为10)&#xff0c;就需要考虑大…

PDF.NET不使用DalFactory和IDAL支持多种数据库应用方案

MS的PetShop示例应用程序的“多层架构”被很多.NET开发人员奉为经典的架构&#xff0c;我以前做的项目团队的Leader也是照搬它的&#xff0c;甚至来到现在这个公司后&#xff0c;好几个新来的同事建解决方案也是照搬PetShop的架构&#xff0c;可见PetShop对大家影响之深。 下面…

javascript 装逼风格(部分)

1.用感叹号将非布尔值转化为布尔值&#xff08;感叹号可以把所有的东西都变成布尔值&#xff09; var str "abc"; console.log(!str);2.双波浪号的妙用&#xff0c;将内容转化为数字,或者小数取整&#xff08;双波浪号的取整是直接去掉小数点后的小数&#xff09; v…

64位LabVIEW64可以调用32位的DLL吗

64位LabVIEW64可以调用32位的DLL吗 在用64位的LabVIEW中调用库函数节点时选择一个32位DLL时&#xff0c;得到一个对话框提示&#xff1a; ​ 编辑 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; ​ 为什么会显示这个错误&#xff1f;可以在64位LabV…

ASP.net 路径问题详解

各位有没有碰到在日常工作中经常在路径设置的时候把 "~/ 、./ 、../ 、 / 、http://www.cnblogs.com/"这些符号搞混搞乱了&#xff1f;偶尔还会因路径的问题郁闷了半天 还以为是程序上出了问题了。以下我是转自--脚本之家 里的一篇技文&#xff0c;略作修改&#xff…

LabVIEW开发汽车惯性导航系统测试

LabVIEW开发汽车惯性导航系统测试 惯性导航单元的测试解决方案由两部分组成&#xff1a; 测试台&#xff1a;它由仪器仪表&#xff0c;测试机架和线束组成。对于仪器仪表&#xff0c;采用双端口电源来为设备和基站供电&#xff0c;而多功能DAQ则使用多路复用器通过RS232接口进…