资讯专栏INFORMATION COLUMN

(二)java多线程之synchronized

Tony / 592人阅读

摘要:本人邮箱欢迎转载转载请注明网址代码已经全部托管有需要的同学自行下载引言现在让我们来考虑一个问题如果要让多个线程来访问同一份数据会发生什么现象呢比如的火车售票系统比如银行的存取款系统等等都可以会出现多线程访问同一个数据的情况让我们先模拟写一个

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

引言

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

编码

首先创建一个Ticket

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

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

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

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个售票点同时售票

public static void main(String[] args) throws InterruptedException {
    List 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方法改为

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不是同一个对象,即各个线程只对自己的门加锁,不能保证是对同一个门加锁.

打赏

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67064.html

相关文章

  • Java线程进阶(十八)—— J.U.Csynchronizer框架:CountDownLatc

    摘要:线程可以调用的方法进入阻塞,当计数值降到时,所有之前调用阻塞的线程都会释放。注意的初始计数值一旦降到,无法重置。 showImg(https://segmentfault.com/img/remote/1460000016012041); 本文首发于一世流云的专栏:https://segmentfault.com/blog... 一、CountDownLatch简介 CountDow...

    Elle 评论0 收藏0
  • java

    摘要:多线程编程这篇文章分析了多线程的优缺点,如何创建多线程,分享了线程安全和线程通信线程池等等一些知识。 中间件技术入门教程 中间件技术入门教程,本博客介绍了 ESB、MQ、JMS 的一些知识... SpringBoot 多数据源 SpringBoot 使用主从数据源 简易的后台管理权限设计 从零开始搭建自己权限管理框架 Docker 多步构建更小的 Java 镜像 Docker Jav...

    honhon 评论0 收藏0
  • 慕课网_《细说Java线程内存可见性》学习总结

    时间:2017年07月09日星期日说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学源码:无学习源码:https://github.com/zccodere/s... 第一章:课程简介 1-1 课程简介 课程目标和学习内容 共享变量在线程间的可见性 synchronized实现可见性 volatile实现可见性 指令重排序 as-if-seria...

    wupengyu 评论0 收藏0
  • Java开发

    摘要:大多数待遇丰厚的开发职位都要求开发者精通多线程技术并且有丰富的程序开发调试优化经验,所以线程相关的问题在面试中经常会被提到。将对象编码为字节流称之为序列化,反之将字节流重建成对象称之为反序列化。 JVM 内存溢出实例 - 实战 JVM(二) 介绍 JVM 内存溢出产生情况分析 Java - 注解详解 详细介绍 Java 注解的使用,有利于学习编译时注解 Java 程序员快速上手 Kot...

    Lucky_Boy 评论0 收藏0
  • Java开发

    摘要:大多数待遇丰厚的开发职位都要求开发者精通多线程技术并且有丰富的程序开发调试优化经验,所以线程相关的问题在面试中经常会被提到。将对象编码为字节流称之为序列化,反之将字节流重建成对象称之为反序列化。 JVM 内存溢出实例 - 实战 JVM(二) 介绍 JVM 内存溢出产生情况分析 Java - 注解详解 详细介绍 Java 注解的使用,有利于学习编译时注解 Java 程序员快速上手 Kot...

    LuDongWei 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<