java---线程安全详解

这篇具有很好参考价值的文章主要介绍了java---线程安全详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

一、线程不安全产生的原因

1.多个线程同时修改一个变量

2.非原子性操作

3.内存可见性问题

4.指令重排序问题

 二、线程安全的解决

1.加锁排队执行

1. 同步锁synchronized

2.可重入锁ReentrantLock

2.原子类AtomicInteger

总结


前言

线程安全是指某个方法或某段代码,在多线程中能够正确的执行,不会出现数据不一致或数据污染的情况,我们把这样的程序称之为线程安全的,反之则为非线程安全的。

一、线程不安全产生的原因

1.多个线程同时修改一个变量

如果是多线程同时修改不同的变量(每个线程只修改自己的变量),也是不会出现非线程安全的问题了,比如以下代码,线程 1 修改 number1 变量,而线程 2 修改 number2 变量,最终两个线程执行完之后的结果如下:

public class test2{
    // 全局变量
    private static int number = 0;
    // 循环次数(100W)
    private static final int COUNT = 1_000_000;
    // 线程 1 操作的变量 number1
    private static int number1 = 0;
    // 线程 2 操作的变量 number2
    private static int number2 = 0;

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 number+1 操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                number1++;
            }
        });
        t1.start();

        // 线程2:执行 100W 次 number-1 操作
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                number2--;
            }
        });
        t2.start();

        // 等待线程 1 和线程 2,执行完,打印 number 最终的结果
        t1.join();
        t2.join();
        number = number1 + number2;
        System.out.println("number=number1+number2 最终结果:" + number);
    }
}

 程序的运行结果:

java---线程安全详解

 从上述结果可以看出,多线程只要不是同时修改同一个变量,也不会出现线程安全问题

2.非原子性操作

原子性操作是指操作不能再被分隔就叫原子性操作。首先,要明确java代码中的一条语句可能代表多个指令,例如 r++ 、r--都是对应多条指令,所以在这里他们的原子性没有得到保证。

java---线程安全详解

以上就是一个经典的错误,r 原本等于 1,线程 1 进行 -1 操作,而线程 2 进行加 1,最终的结果 n,r 应该还等于 1 才对,但通过上面的执行,number 最终被修改成了 0,这就是非原子性导致的问题。

3.内存可见性问题

在 Java 编程中内存分为两种类型:工作内存和主内存,而工作内存使用的是 CPU 寄存器实现的,而主内存是指电脑中的内存,我们知道 CPU 寄存器的操作速度是远大于内存的操作速度的,它们的性能差异如下图所示:

java---线程安全详解

 在 Java 语言中,为了提高程序的执行速度,所以在操作变量时,会将变量从主内存中复制一份到工作内存,而主内存是所有线程共用的,工作内存是每个线程私有的,这就会导致一个线程已经把主内存中的公共变量修改了,而另一个线程不知道,依旧使用自己工作内存中的变量,这样就导致了问题的产生,也就导致了线程安全问题。

4.指令重排序问题

例如在实例化对象的时候:

SomeObeject  so = new SomeObject();

上述代码可以分为三步:

1.根据类计算对象的大小:在堆内存中分配内存空间给该对象。

2.对对象进行初始化(构造代码块、构造方法)。

3.把引用交给 so。

但是由于代码重排序的原因,将本来的顺序变为 1 -> 3 ->2 ;假设在执行到 3的时候,发生了线程调度,假如此时新来的线程需要 so对象,所以就使用了,但是,因为代码重排序的问题,上一个线程还没有进行对象的初始化,所以导致发生错误。

 二、线程安全的解决

1.加锁排队执行

Java 中有两种锁:synchronized 同步锁和 ReentrantLock 可重入锁。

1. 同步锁synchronized

synchronized 是 JVM 层面实现的自动加锁和自动释放锁的同步锁,它的实现代码如下:

public class Test2 {
    // 全局变量
    private static int number = 0;
    // 循环次数(100W)
    private static final int COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 ++ 操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                // 加锁排队执行
                synchronized (Test2.class) {
                    number++;
                }
            }
        });
        t1.start();

        // 线程2:执行 100W 次 -- 操作
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                // 加锁排队执行
                synchronized (Test2.class) {
                    number--;
                }
            }
        });
        t2.start();

        // 等待线程 1 和线程 2,执行完,打印 number 最终的结果
        t1.join();
        t2.join();
        System.out.println("number 最终结果:" + number);
    }
}

 运行结果如下:

java---线程安全详解

2.可重入锁ReentrantLock

ReentrantLock 可重入锁需要程序员自己加锁和释放锁,它的实现代码如下: 

public class Test2{
    // 全局变量
    private static int number = 0;
    // 循环次数(100W)
    private static final int COUNT = 1_000_000;
    // 创建 ReentrantLock
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 ++ 操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                lock.lock();    // 手动加锁
                number++;       // ++ 操作
                lock.unlock();  // 手动释放锁
            }
        });
        t1.start();

        // 线程2:执行 100W 次 -- 操作
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                lock.lock();    // 手动加锁
                number--;       // -- 操作
                lock.unlock();  // 手动释放锁
            }
        });
        t2.start();

        // 等待线程 1 和线程 2,执行完,打印 number 最终的结果
        t1.join();
        t2.join();
        System.out.println("number 最终结果:" + number);
    }
    
}


 程序运行结果如下:

java---线程安全详解

 

2.原子类AtomicInteger

AtomicInteger 是线程安全的类,使用它可以将 ++ 操作和 -- 操作,变成一个原子性操作,这样就能解决非线程安全的问题了,如下代码所示:

public class Test2 {
    // 创建 AtomicInteger
    private static AtomicInteger number = new AtomicInteger(0);
    // 循环次数
    private static final int COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 ++ 操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                // ++ 操作
                number.incrementAndGet();
            }
        });
        t1.start();

        // 线程2:执行 100W 次 -- 操作
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                // -- 操作
                number.decrementAndGet();
            }
        });
        t2.start();

        // 等待线程 1 和线程 2,执行完,打印 number 最终的结果
        t1.join();
        t2.join();
        System.out.println("number 最终结果:" + number.get());
    }
}

程序的运行结果:

java---线程安全详解


 

总结

加油偶~~文章来源地址https://www.toymoban.com/news/detail-480418.html

到了这里,关于java---线程安全详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JAVA安全之目录遍历漏洞

    路径(目录)遍历是攻击者能够访问或存储应用程序运行位置之外的文件和目录的漏洞。这可能会导致从其他目录读取文件,并且在文件上传覆盖关键系统文件的情况下。 例如,假设我们有一个托管一些文件的应用程序,并且可以按以下格式请求它们:http://example.com/file=re

    2024年02月08日
    浏览(33)
  • Java多线程 - 线程安全和线程同步解决线程安全问题

    线程安全问题指的是: 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。 举例: 取钱模型演示 需求:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元。 如果小明和小红同时来取钱,而且2人都要取钱10万元,可能出现什么问

    2023年04月15日
    浏览(31)
  • 【Linux】线程安全(万字详解)

    🎇Linux: 博客主页: 一起去看日落吗 分享博主的在Linux中学习到的知识和遇到的问题 博主的能力有限,出现错误希望大家不吝赐教 分享给大家一句我很喜欢的话: 看似不起波澜的日复一日,一定会在某一天让你看见坚持的意义,祝我们都能在鸡零狗碎里找到闪闪的快乐🌿

    2024年02月02日
    浏览(37)
  • 41 JAVA安全-目录遍历访问控制XSS等安全问题

    Javaweb代码分析-目录遍历安全问题 代码解析及框架源码追踪: 第一关: Payload: …/x …/相当于跨越上级目录的符号,…/x可以更改默认上传文件的路径 通过命名文件的名字,在文件名命名加入路径符号,来实现将这个文件在上传路径上的更改 目录解析,如果对方设置了目录解

    2024年01月21日
    浏览(28)
  • java基础之线程安全问题以及线程安全集合类

    当多个线程同时访问同一个临界资源时,原子操作可能被破坏,会导致数据丢失, 就会触发线程安全问题 临界资源: 被多个线程同时访问的对象 原子操作: 线程访问临界资源的过程中不可更改和缺失的操作 互斥锁 每个对象都默认拥有互斥锁, 该锁默认不开启. 当开启互斥锁之后

    2024年01月18日
    浏览(40)
  • Java 多线程之线程安全集合

    集合关系图 本文主要关注线程安全的集合,如 List、Set、Queue、Map 等接口的线程安全的实现方式,有关集合基础知识请转到这里。所谓线程安全集合,就是在多线程环境中使用集合不会导致数据不一致和数据异常的集合。在 Java 中线程安全集现在基本都使用 java.util.concurrent

    2024年02月05日
    浏览(35)
  • Java多线程之线程安全问题

    我们知道操作系统中线程程的调度是抢占式执行的, 宏观上上的感知是随机的, 这就导致了多线程在进行线程调度时线程的执行顺序是不确定的, 因此多线程情况下的代码的执行顺序可能就会有无数种, 我们需要保证这无数种线程调度顺序的情况下, 代码的执行结果都是正确的

    2023年04月17日
    浏览(36)
  • java多线程之线程安全(重点,难点)

    由于操作系统中,线程的调度是抢占式执行的,或者说是随机的,这就造成线程调度执行时,线程的执行顺序是不确定的,虽然有一些代码在这种执行顺序不同的情况下也不会运行出错,但是还有一部分代码会因为执行顺序发生改变而受到影响,这就会造成程序出现Bug,对于多线程并发

    2024年01月25日
    浏览(34)
  • Java中的多线程——线程安全问题

    作者:~小明学编程   文章专栏:JavaEE 格言:热爱编程的,终将被编程所厚爱。 目录 多线程所带来的不安全问题 什么是线程安全 线程不安全的原因 修改共享数据 修改操作不是原子的 内存可见性对线程的影响 指令重排序 解决线程不安全的问题 synchronized 互斥 刷新内

    2024年02月03日
    浏览(60)
  • Java多线程(2)---线程控制和线程安全的详细讲解

    目录 前言 一.线程控制方法 1.1启动线程--start() 1.2线程睡眠---sleep()方法 1.3中断线程--interrupt() 方法 1.4等待线程---join() 二.线程安全  2.1数据不安全---数据共享 ⭐不安全的演示和原因  ⭐不安全的处理方法 ⭐synchronized的使用 2.2数据不安全---内存可见性 ⭐不安全的演示和原因

    2024年02月13日
    浏览(23)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包