Java中的线程安全问题详细解析

 更新时间:2023年11月17日 10:16:58   作者:牧码ya  
这篇文章主要介绍了Java中的线程安全问题详细解析,线程安全是如果有多个线程在同时运行,而这些线程可能会同时运行这段代码,程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,此时我们就称之为是线程安全的,需要的朋友可以参考下

线程安全

线程安全:如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,此时我们就称之为是线程安全的。

我们通过一个案例,演示线程的安全问题:

电影院卖票,使用了A、B、C三个窗口进行卖票,电影票总数为100张

采用线程对象来模拟卖票窗口A、B、C;使用Runnable接口的子类来模拟买的电影票

模拟电影票:

public class Ticket implements Runnable{
    // 在成员位置 定义票的总数100
    int ticket = 100;

    @Override
    public void run() {
        // 模拟买票窗口
        // 买票窗口永远开启
        while (true){
            // 判断是否还有票可以卖
            if(ticket > 0){
                // 使用sleep增加“程序的时间”--每张票卖50ms
                try {
                    Thread.sleep(50);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                // 获得线程名称 即买票窗口名称
                String name = Thread.currentThread().getName();
                System.out.println(name + "卖掉第" + ticket-- + "票");
            }
        }
    }
}

模拟买票:

/**
 * 模拟买票操作
 *    假设一场电影有100张票
 *    三个窗口同时买票
 *
 *    窗口  线程对象
 *    买票  线程任务 实现runnable接口
 */
public class Demo {
    public static void main(String[] args) {
        // 创建买票任务对象
        Ticket ticket = new Ticket();

        // 创建三个窗口
        Thread t1 = new Thread(ticket, "窗口A");
        Thread t2 = new Thread(ticket, "窗口B");
        Thread t3 = new Thread(ticket, "窗口C");

        // 开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

窗口A卖掉第100张票
窗口C卖掉第98张票
窗口B卖掉第99张票
窗口A卖掉第97张票
窗口B卖掉第95张票
窗口C卖掉第96张票
窗口C卖掉第94张票 ⇐
窗口B卖掉第94张票 ⇐
窗口A卖掉第94张票 ⇐
...
窗口C卖掉第1张票
窗口A卖掉第0张票
窗口B卖掉第-1张票 ⇐

发现程序出现了两个问题:

1. 相同的票数被卖了多次,如第94张被三个窗口都卖了

2. 卖出了不存在的票,如窗口B卖掉了第-1张票

此时,几个窗口(线程)票数不同步了,这种问题称为线程不安全。

线程安全问题都是有全局变量即静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作。一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全

线程同步

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有些的操作,就容易出现线程安全问题

要解决上述多想成并发访问一个资源的安全性问题:也就是解决重复卖同一张票和卖不存在的票问题,Java中提供了同步机制(synchronized)来解决

根据案例简述:

窗口A线程进入操作(买票)的时候,窗口B和窗口C线程只能在外等着,窗口A操作结束,窗口A、窗口B和窗口C(CPU分配内存是随机的,所以还有可能是窗口A进入)才有机会进入代码去执行。

也就是说,在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。

有三种方式完成同步操作:

1. 同步代码块

2. 同步方法

3. 锁机制

同步代码块

同步代码块:synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){
    // 需要同步的操作的代码
}

同步锁:

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。

1. 锁对象可以是任意类型

2. 多个线程对象要使用同一把锁

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到就进入代码块,其他的线程只能在外等着

使用同步代码块解决卖票问题:

/**
 * synchronized(锁对象){
 *
 * }
 * 1. 锁对象可以是任意类型
 * 2. 互斥线程需要使用同一把锁
 */
public class Ticket implements Runnable{
    // 在成员位置 定义票的总数100
    int ticket = 100;

    Object obj = new Object();

    @Override
    public void run() {
        // 模拟买票窗口
        // 买票窗口永远开启
        while (true){
            // 同步锁
            synchronized (obj){
                // 判断是否还有票可以卖
                if(ticket > 0){
                    // 使用sleep增加“程序的时间”--每张票卖50ms
                    try {
                        Thread.sleep(50);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    // 获得线程名称 即买票窗口名称
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "卖掉第" + ticket-- + "票");
                }
            }

        }
    }
}

执行结果:

窗口A卖掉第100票
窗口C卖掉第99票
窗口B卖掉第98票
窗口B卖掉第97票
...
窗口C卖掉第4票
窗口A卖掉第3票
窗口A卖掉第2票
窗口A卖掉第1票

此时,每张票都只会被卖掉一次,不会存在卖掉不存在的电影票的问题。

当使用了同步代码块后,上述的线程的安全问题即可解决

到此这篇关于Java中的线程安全问题详细解析的文章就介绍到这了,更多相关Java线程安全内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 浅析Java中静态代理和动态代理的应用与区别

    浅析Java中静态代理和动态代理的应用与区别

    代理模式在我们生活中很常见,而Java中常用的两个的代理模式就是动态代理与静态代理,这篇文章主要为大家介绍了二者的应用与区别,需要的可以参考下
    2023-08-08
  • 手动构建springBoot启动器过程图解

    手动构建springBoot启动器过程图解

    这篇文章主要介绍了手动构建springBoot启动器过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • 五分钟带你了解Java的接口数据校验

    五分钟带你了解Java的接口数据校验

    这篇文章主要介绍了五分钟带你了解Java的接口数据校验,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • springBoot解决static和@Component遇到的bug

    springBoot解决static和@Component遇到的bug

    这篇文章主要介绍了springBoot解决static和@Component遇到的bug,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Java设计模式之原型模式(Prototype模式)介绍

    Java设计模式之原型模式(Prototype模式)介绍

    这篇文章主要介绍了Java设计模式之原型模式(Prototype模式)介绍,本文讲解了如何使用原型模式并给出了代码实例,需要的朋友可以参考下
    2015-03-03
  • Spring Native实现0.059s启动一个SpringBoot项目

    Spring Native实现0.059s启动一个SpringBoot项目

    Spring Native是Spring框架的一个子项目,旨在提供一种将Spring应用程序编译为本地可执行文件的方法,从而提高启动时间和资源效率,本文主要介绍了Spring Native实现0.059s启动一个SpringBoot项目,感兴趣的可以了解一下
    2024-02-02
  • java解析sina视频

    java解析sina视频

    本文介绍了一个java解析sina视频地址的例子,从这个例子中可以学习到java使用sax解析xml的方法,大家可以参考修改成其它功能
    2014-01-01
  • 使用MockMvc进行controller层单元测试 事务自动回滚的完整案例

    使用MockMvc进行controller层单元测试 事务自动回滚的完整案例

    这篇文章主要介绍了使用MockMvc进行controller层单元测试 事务自动回滚的完整案例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • java微信开发API第一步 服务器接入

    java微信开发API第一步 服务器接入

    这篇文章主要为大家分享了java微信开发API的第一步操作服务器接入,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • SpringBoot错误处理机制以及自定义异常处理详解

    SpringBoot错误处理机制以及自定义异常处理详解

    这篇文章主要为大家详细介绍了SpringBoot错误处理机制以及自定义异常处理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-05-05

最新评论