使用同步解决多人卖票问题 | 带你学《Java语言高级特性》之九

简介: 本节先由多人合卖10张票为例,引出了线程同步问题,并结合实例代码讲解了同步机制的实现办法。

上一篇:迅速读懂Java线程的交通规则 | 带你学《Java语言高级特性》之八
【本节目标】
通过阅读本节内容,你将了解到线程同步问题出现的原因,并学会使用synchronized关键字实现多个线程同时只有一个能进行调用的限制,解决线程同步问题。

在多线程的处理之中,可以利用Runnable描述多个线程操作的资源,而Thread描述每一个线程对象,当然多个线程访问同一资源时如果处理不当就会产生数据的错误操作。

同步问题的引出

下面编写一个简单的卖票程序,将若干个线程对象实现卖票的处理操作。
范例:实现卖票操作

class MyThread implements Runnable {
    private int ticket = 10 ;        //总票数为10张
    @Override
    public void run() {
        while (true) {
            if (this.ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--) ;
            } else {
                System.out.println("***** 票已经卖光了 *****") ;
                break ;
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        MyThread mt=new MyThread() ;
        new Thread(mt,"票贩子A").start() ;
        new Thread(mt,"票贩子B").start() ;
        new Thread(mt,"票贩子C").start() ;
    }
}

image.png
图一 卖票程序执行

此时的程序将创建3个线程对象,并且这三个线程将进行10张票的出售。此时的程序在进行卖票处理的时候,并没有任何的问题(假象),下面可以模拟一下卖票中的延迟操作。

class MyThread implements Runnable {
    private int ticket = 10;       //总票数为10张
    @Override
    public void run() {
        while (true) {
            if (this.ticket > 0) {
                try {
                    Thread.sleep(100);    //模拟网络延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
            } else {
                System.out.println("***** 票已经卖光了 *****");
                break;
            }
        }
    }
}

image.png
图二 卖票问题执行操作

这个时候追加了延迟,问题就暴露出来了,而实际上这个问题一直都在。

image.png
图三 卖票处理

线程同步处理

经过分析之后已经可以确定同步问题产生的主要原因了,那么下面就需要进行同步问题的解决,但是解决同步问题的关键是锁,指的是当某一个线程执行操作的时候,其他线程外面等待;

image.png
图四 问题的解决

如果要想程序中实现这把锁功能,就可以使用synchronized关键字来实现,利用此关键字可以定义同步方法或同步代码块,在同步代码块的操作中的代码只允许一个线程执行。
1、利用同步代码块进行处理:

synchronized (同步对象){
    同步代码操作;
}

一般要进行同步对象处理的时候可以采用当前对象this进行同步。
范例:利用同步代码块解决数据同步访问问题

class MyThread implements Runnable {
    private int ticket = 10 ;    //总票数为10张
    @Override
    public void run() {
        while (true) {
            synchronized (this) {    //每一次只允许一个线程进行访问
                if (this.ticket > 0) {
                    try {
                        Thread.sleep(100);     //模拟网络延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
                } else {
                    System.out.println("***** 票已经卖光了 *****");
                    break;
                }
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        MyThread mt = new MyThread();
        new Thread(mt, "票贩子A").start();
        new Thread(mt, "票贩子B").start();
        new Thread(mt, "票贩子C").start();
    }
}

加入同步处理之后,程序的整体性能下降了。同步实际上会造成性能的降低。

2、利用同步方法解决:只需要在方法定义上使用synchronized关键字即可。

class MyThread implements Runnable {
    private int ticket = 10;    //总票数为10张
    public synchronized boolean sale() {
        if (this.ticket > 0) {
            try {
                Thread.sleep(100);    //模拟网络延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
            return true;
        } else {
            System.out.println("***** 票已经卖光了 *****");
            return false;
        }
    }
    @Override
    public void run() {
        while ( this.sale()) {}
    }
}
public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        MyThread mt = new MyThread();
        new Thread(mt, "票贩子A").start();
        new Thread(mt, "票贩子B").start();
        new Thread(mt, "票贩子C").start();
    }
}

image.png
图五 解决同步问题

在日后学习Java类库的时候,系统中许多的类上使用的同步处理采用的都是同步方法。

想学习更多的Java的课程吗?从小白到大神,从入门到精通,更多精彩不容错过!免费为您提供更多的学习资源。
本内容视频来源于阿里云大学

下一篇:同步的缺陷-死锁问题 | 带你学《Java语言高级特性》之十
更多Java面向对象编程文章查看此处

相关文章
|
10天前
|
安全 Java 大数据
探索Java的奇妙世界:语言特性与实际应用
探索Java的奇妙世界:语言特性与实际应用
|
26天前
|
前端开发 Java C#
java/C#语言开发的医疗信息系统11套源码
java/C#语言开发的医疗信息系统11套源码
19 1
|
7天前
|
Java
Java语言---面向对象的三大特征之继承
Java语言---面向对象的三大特征之继承
|
7天前
|
存储 Java
Java语言--->数据类型转化以及运算符
本文讲述了Java中的基本数据类型转换和运算符。在转换中,布尔型除外的8种类型可通过默认或强制转换相互转化,如`byte->short->int->long->float->double`。默认转换发生在不同类型运算时,系统会转为更大容量类型。强制转换可能引起精度损失或溢出。运算符包括算术(如+可做加法或字符串拼接)、比较、逻辑和赋值运算符。比较运算符如`==`、`<`等产生`boolean`结果,逻辑运算符`&&`、`||`具有短路效应。赋值运算符如`+=`简化了赋值过程。运算符的优先级也进行了简要说明。
|
7天前
|
Java 编译器 程序员
Java语言基础
Java语言基础概览:涵盖关键字(全小写,如`image.png`所示),保留字(如`goto`、`const`),标识符命名规则(避免数字开头和保留字),注释(单行`//`,多行`/*...*/`,及Javadoc注释)以及变量(按数据类型:byte, short, int, long, float, double, char, boolean,注意声明规则和默认类型)。
|
9天前
|
安全 算法 Java
写给Java开发的Go语言协程实践
写给Java开发的Go语言协程实践
14 0
|
11天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.2数据库编程——了解SQL语言,编写基本查询语句
Java从入门到精通:2.3.2数据库编程——了解SQL语言,编写基本查询语句
|
17天前
|
前端开发 Java Go
开发语言详解(python、java、Go(Golong)。。。。)
开发语言详解(python、java、Go(Golong)。。。。)
|
17天前
|
人工智能 前端开发 Java
Java语言开发的AI智慧导诊系统源码springboot+redis 3D互联网智导诊系统源码
智慧导诊解决盲目就诊问题,减轻分诊工作压力。降低挂错号比例,优化就诊流程,有效提高线上线下医疗机构接诊效率。可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。
164 10
|
19天前
|
存储 缓存 安全
Java并发基础之互斥同步、非阻塞同步、指令重排与volatile
在Java中,多线程编程常常涉及到共享数据的访问,这时候就需要考虑线程安全问题。Java提供了多种机制来实现线程安全,其中包括互斥同步(Mutex Synchronization)、非阻塞同步(Non-blocking Synchronization)、以及volatile关键字等。 互斥同步(Mutex Synchronization) 互斥同步是一种基本的同步手段,它要求在任何时刻,只有一个线程可以执行某个方法或某个代码块,其他线程必须等待。Java中的synchronized关键字就是实现互斥同步的常用手段。当一个线程进入一个synchronized方法或代码块时,它需要先获得锁,如果
26 0