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中的EnumMap和EnumSet

    深入理解Java中的EnumMap和EnumSet

    这篇文章主要介绍了深入理解Java中的EnumMap和EnumSet,一般来说我们会选择使用HashMap来存储key-value格式的数据,考虑这样的特殊情况,一个HashMap的key都来自于一个Enum类,这样的情况则可以考虑使用本文要讲的EnumMap,需要的朋友可以参考下
    2023-11-11
  • Mybatis之RowBounds分页原理详解

    Mybatis之RowBounds分页原理详解

    这篇文章主要介绍了Mybatis之RowBounds分页原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • java生成图片验证码返回base64图片信息方式

    java生成图片验证码返回base64图片信息方式

    这篇文章主要介绍了java生成图片验证码返回base64图片信息方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • 基于spring三方包类注入容器的四种方式小结

    基于spring三方包类注入容器的四种方式小结

    这篇文章主要介绍了基于spring三方包类注入容器的四种方式小结,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Java实现爬虫给App提供数据(Jsoup 网络爬虫)

    Java实现爬虫给App提供数据(Jsoup 网络爬虫)

    这篇文章主要介绍了Java实现爬虫给App提供数据,即Jsoup 网络爬虫,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-01-01
  • springboot实现配置两个parent的方法

    springboot实现配置两个parent的方法

    这篇文章主要介绍了springboot实现配置两个parent的方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java Stream API详解与使用示例详解

    Java Stream API详解与使用示例详解

    Java Stream API 是一个功能强大的工具,适用于处理集合和数据流,本文全面介绍了 Java Stream API 的概念、功能以及如何在 Java 中有效地使用它进行集合和数据流的处理,感兴趣的朋友跟随小编一起看看吧
    2024-05-05
  • Spring Boot thymeleaf模板引擎的使用详解

    Spring Boot thymeleaf模板引擎的使用详解

    这篇文章主要介绍了Spring Boot thymeleaf模板引擎的使用详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • Java中的字符串常量池详细介绍

    Java中的字符串常量池详细介绍

    这篇文章主要介绍了Java中的字符串常量池详细介绍,JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池,需要的朋友可以参考下
    2015-01-01
  • java 中 poi解析Excel文件版本问题解决办法

    java 中 poi解析Excel文件版本问题解决办法

    这篇文章主要介绍了java 中 poi解析Excel文件版本问题解决办法的相关资料,需要的朋友可以参考下
    2017-08-08

最新评论