iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >一文搞懂Java中的线程安全与线程同步
  • 247
分享到

一文搞懂Java中的线程安全与线程同步

2024-04-02 19:04:59 247人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录1.为什么需要线程同步线程安全问题2.怎么实现线程同步2.1.使用volatile关键字2.2.使用synchronized关键字1.为什么需要线程同步 什么是线程安全:指在被多

1.为什么需要线程同步

什么是线程安全:指在被多个线程访问时,程序可以持续进行正确的处理。

线程安全问题

案例:通过抢优惠例子说明线程安全问题

图片

public class Demo1 {

    public static void main(String[] args) {
        // 简单模拟20人抢优惠
        for(int i=0;i<20;i++){
            new Thread(new ThreadDemo()).start();
        }
    }

}
// 前十位可以获取优惠,凭号码兑换优惠
class ThreadDemo implements Runnable{
    private static Integer num = 10;
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(num<=0){
            System.out.println("已被抢完,下次再来");
            return;
        }
        System.out.println(Thread.currentThread().getName()+"用户抢到的号码:"+num--);
    }
}

执行结果:出现的问题

  • 同一个优惠号码可能被多次获取到;
  • 优惠号码可能获取到0 和负数,类似超买超卖

并发访问线程不安全的共享变量时,会出现如上的常见问题。

避免问题的产生

  • 共享变量设计为不可变的(final)
  • 编程过程不修改共享变量(不修改)
  • 将对象设计为无状态的(无状态)
  • 在修改共享变量时使用线程同步(通过实现)

2.怎么实现线程同步

线程同步是指程序中用于控制不同线程间操作发生相对顺序的机制。

2.1.使用volatile关键字

volatile是轻量级的synchronized,对于共享变量可以通过volatile关键字来实现线程同步。

共享变量处理情况:总线先从主内存拷贝共享变量到线程私有的工作内存,再交由处理器进行处理,处理完成后再从工作内存将运算结果回写主内存。工作内存起到临时缓存数据和指令的作用、并且线程私有,这就会存在缓存不一致问题。

实现原理:有volatile变量修饰的共享变量进行写操作的时候会多出一行lock 前缀指令的汇编代码,lock前缀指令会直接锁缓存行,起到内存屏障的效果,并使处理器立即执行缓存回写到主内存的操作,并且导致其他处理器的缓存失效,需要重新从主内存获取最新值。

附上一张图便于理解

图片

Java线程需要由操作系统内核线程调度器进行调度,并不是直接访问处理器资源,图中仅展示关键的几个点。

怎么输出汇编指令

windows:下载hsdis-amd64.dll 提取码:w5a2,并放在目录 jdk1.8.0_181\jre\bin\server

idea 工具中配置VM:-Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*TestVolatile.main

// 测试代码
public class TestVolatile {
    private static volatile int num = 0;

    public static void main(String[] args) {
        num--;
        System.out.println("结果为:" + num);
    }
}

执行结果:lock add dWord ptr [rsp]

0x000000000320606d: lock add dword ptr [rsp],0h ;*putstatic num ; - com.yty.concurrent.synchronizeddemo.TestVolatile::main@5 (line 7)

通过缓存锁定和缓存一致性协议实现可见性,确保多线程程序读写共享变量的时候,每个CPU看到的都是最新值。

MESI 高速缓存一致性协议,处理器使用嗅探技术保证缓存、主内存和其他处理器缓存的数据一致。还有其他的缓存一致性协议,比如:AMD的MOESI协议、Intel的MOSIF协议。

MESI 分别表示:

  • M(Modify):修改
  • E(Exclusive):独占、互斥
  • S(Shared):共享
  • I(Invalid):无效

volatile 关键字的另一个作用是禁止编译器或处理器对指令进行重排序优化

什么是重排序:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。说白了,如果在编译或运行期间发生重排序,那么程序就可能不按照编写的顺序执行,会出现意料之外的执行结果。

编译器和处理器不会对存在数据依赖关系的操作做重排序,一般重排序可能发生在没有数据依赖关系的指令之中。就算数据之间没有依赖关系,在多线程场景中若发生指令重排序优化,依然存在影响到最终执行结果的情况。当多线程场景下存在这种情况时,就需要使用volatile关键字等其他手段去禁止编译器和处理器对指令重排序优化。

实现原理是在编译器在生成字节码时,会在指令序列中插入内存屏障禁止在内存屏障前后的指令执行重排序优化。

回来到一开始的案例问题,给线程类的共享变量 private static Integer num = 10; 加上volatile关键字能实现线程安全吗?

private static volatile Integer num = 10;

答案是:不能

原因是:指令【num--】看似一条指令,其实分成三步执行:先获取、再计算、后保存,所以自减和自增都不是原子性操作,而volatile 无法确保其原子性操作,所以使用volatile关键字无法确保该情况下的线程安全,需要使用锁来实现原子性操作。

2.2.使用synchronized关键字

synchronized块是Java提供的一种原子性内置锁,Java中的每个对象都可以把它当作一个同步锁来使用,也称为监视器锁。synchronized 同步代码块中的操作被看为原子性操作。同步锁是一种排它锁、独占锁、可重入锁,当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁,并支持锁释放后可以再次获取锁。

使用方式

private Integer num = 1;
private static Integer num2 = 1;

public synchronized void test1(){
    System.out.println("普通方法" + num++);
}
public static synchronized void test2(){
    System.out.println("静态方法" + num2++);
}

public void test1_1(){
    synchronized (this){
        System.out.println("锁当前对象"+ num++);
    }
    synchronized (TestSynchronized.class){
        System.out.println("锁当前类" + num++);
    }
    synchronized (num){
        System.out.println("锁指定变量" + num++);
    }
}

synchronized 关键字加在普通方法时,是给当前实例对象上锁;

synchronized 关键字加在静态方法时,是给当前Class类对象上锁;

synchronized 关键字加在同步代码块时,是给括号里配置的对象上锁。

注意:这个Class类对象指的是每个类在类加载过程中生成的Class类。

原理简析

在synchronized的同步代码块中,入口处执行了monitorenter,在其出口执行了monitorexit,虚拟机中的每个Object实例都有一个monitor(监视器锁);而同步方法通过字节码flags 标记该方法为ACC_SYNCHRONIZED,表明执行该方法时需要获取到监视器锁才可以执行。两者的本质都是对一个对象的监视器(monitor)的获取和释放。

可以使用javap -c -v xxx.class 查看字节码信息

同步代码块

图片

同步方法

图片

关于一开始的例子,可以写成

public class Demo1 {

    public static void main(String[] args) {
        // 简单模拟20人抢优惠
        for(int i=0;i<20;i++){
            new Thread(new ThreadDemo()).start();
        }
    }

}
// 前十位可以获取优惠,凭号码兑换优惠
class ThreadDemo implements Runnable{
    private static Integer num = 10;
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (num){
            if(num<=0){
                System.out.println("已被抢完,下次再来");
                return;
            }
            System.out.println(Thread.currentThread().getName()+"用户抢到的号码:"+num--);
        }
    }
}

或者改为

synchronized (ThreadDemo.class){
    if(num<=0){
        System.out.println("已被抢完,下次再来");
        return;
    }
    System.out.println(Thread.currentThread().getName()+"用户抢到的号码:"+num--);
}

注意:num 变量是static变量,是属于类的变量,需要锁住变量对象或Class类,单独或组合使用synchronized (this) 和volatile都无法确保其线程安全,这个可自行验证。

案例:对象单例实现

public class SingletonDemo {
    private static volatile SingletonDemo singletonDemo;
    public SingletonDemo(){
    }
    public static SingletonDemo getInstance(){
        if(singletonDemo==null)
            synchronized (SingletonDemo.class){
                if (singletonDemo==null)
                    singletonDemo = new SingletonDemo();
            }
        return singletonDemo;
    }
}
// 测试
class Demo{
    public static void main(String[] args) {
        Demo demo = new Demo();
        for(int i=0;i<10000;i++){
            demo.test();
        }
    }
    public void test(){
        new Thread(()->{
            SingletonDemo instance = SingletonDemo.getInstance();
            System.out.println(Thread.currentThread().getName()+"="+instance);
        }).start();
    }
}

说明:volatile 和 synchronized实现双重锁校验。synchronized 起到指令执行的原子性和同一时间只能单个线程执行;synchronized和volatile都起到内存可见性保证;volatile起到禁止指令重排序,指令重排序导致线程不安全的可能性较小,但存在可能发生。

除了简单易用的synchronized 同步锁之外,还有其他更灵活的锁。

篇幅原因,将在下一篇讲述关于Java中的锁:

图片

以上就是一文搞懂Java中的线程安全与线程同步的详细内容,更多关于Java线程安全 线程同步的资料请关注编程网其它相关文章!

--结束END--

本文标题: 一文搞懂Java中的线程安全与线程同步

本文链接: https://www.lsjlt.com/news/152414.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 一文搞懂Java中的线程安全与线程同步
    目录1.为什么需要线程同步线程安全问题2.怎么实现线程同步2.1.使用volatile关键字2.2.使用synchronized关键字1.为什么需要线程同步 什么是线程安全:指在被多...
    99+
    2022-11-13
  • 一文彻底搞懂java多线程和线程池
    目录 什么是线程 一. Java实现线程的三种方式1.1、继承Thread类1.2、实现Runnable接口,并覆写run方法二. Callable接口...
    99+
    2022-11-12
  • 一文搞懂Python中的进程,线程和协程
    目录1.什么是并发编程2.进程与多进程3.线程与多线程4.协程与多协程5.总结1.什么是并发编程 并发编程是实现多任务协同处理,改善系统性能的方式。Python中实现并发编程主要依靠...
    99+
    2022-11-10
  • 一文带你搞懂Java中线程的创建方式
    目录一. 继承Thread二. 创建Runnable对象三. 创建Callable对象四. 基于Runnable创建FutureTask总结一. 继承Thread 可以通过创建Thr...
    99+
    2023-03-06
    Java线程创建方式 Java线程创建 Java线程
  • 一文搞懂Java创建线程的五种方法
    目录题目描述解题思路代码详解第一种 继承Thread类创建线程第二种:实现Runnable接口创建线程第三种:实现Callable接口,通过FutureTask包装器来创建Threa...
    99+
    2022-11-13
  • Java多线程 - 线程安全和线程同步解决线程安全问题
    文章目录 线程安全问题线程同步方式一: 同步代码块方式二: 同步方法方式三: Lock锁 线程安全问题 线程安全问题指的是: 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。 举例:...
    99+
    2023-08-20
    java 安全 jvm
  • Java线程安全与同步实例分析
    本篇内容介绍了“Java线程安全与同步实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!线程安全问题多个线程可能会共享(访问)同一个资源...
    99+
    2023-06-30
  • 一篇文章帮你搞懂什么是java的进程和线程
    目录为什么会有进程进程的概念进程的特征线程线程的结构进程之中创建线程的优点进程和线程的区别进程和线程之间的关系总结为什么会有进程 在简单的批处理操作系统中,作业时串行执行的,即一个作...
    99+
    2022-11-12
  • 一篇文章带你搞懂Java线程池实现原理
    目录1. 为什么要使用线程池2. 线程池的使用3. 线程池核心参数4. 线程池工作原理5. 线程池源码剖析5.1 线程池的属性5.2 线程池状态5.3 execute源码5.4 wo...
    99+
    2022-11-13
    Java线程池实现原理 Java线程池原理 Java线程池实现 Java线程池
  • Java并发编程:同步机制与线程安全探究
    在现代软件开发中,多线程编程已经成为了必不可少的一部分。在Java中,线程的使用非常方便,但是也带来了一些问题,比如线程安全问题。为了解决这些问题,Java提供了一些同步机制。在本文中,我们将探究Java中的同步机制和线程安全。 一、线程...
    99+
    2023-11-09
    同步 numy 日志
  • Java使用线程同步解决线程安全问题详解
    第一种方法:同步代码块: 作用:把出现线程安全的核心代码上锁 原理:每次只能一个线程进入,执行完毕后自行解锁,其他线程才能进来执行 锁对象要求:理论上,锁对象只要对于当前同时执行的线...
    99+
    2022-11-13
  • Java 同步工具与组合类的线程安全性解析
    目录何为线程安全的类?基于条件的同步策略状态发布与所有权实例封闭正确地拓展同步策略同步容器复合操作不受同步容器保护同步容器的迭代问题警惕隐含迭代的操作并发容器ConcurrentHa...
    99+
    2022-11-13
  • 一文详解Java线程中的安全策略
    目录一、不可变对象二、线程封闭三、线程不安全类与写法四、线程安全-同步容器1. ArrayList -> Vector, Stack2. HashMap -> HashT...
    99+
    2022-11-13
  • Java详细讲解线程安全与同步附实例与注释
    目录线程安全问题实例:存钱取钱问题买票问题线程安全问题分析问题解决方案线程同步同步语句synchronize(obj)的原理同步方法同步方法的本质线程安全问题 多个线程可能会共享(访...
    99+
    2022-11-13
  • Java中多线程、线程同步与死锁的一些不为人知的秘密
    今天就跟大家聊聊有关Java中多线程、线程同步与死锁的一些不为人知的秘密,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1.线程同步多线程引发的安全问题一个非常经典的案例,银行取钱的问...
    99+
    2023-05-31
    java 多线程 线程同步
  • 一文带你弄懂Java中线程池的原理
    目录为什么要用线程池线程池的原理ThreadPoolExecutor提供的构造方法ThreadPoolExecutor的策略线程池主要的任务处理流程ThreadPoolExecuto...
    99+
    2022-12-08
    Java线程池原理 Java线程池
  • Java中的同步机制:如何确保多线程安全?
    在Java中,多线程的应用非常广泛。然而,多线程同时访问共享资源时,很容易引起数据竞争等问题,导致程序出现未知的错误。因此,在多线程编程中,保证线程安全非常重要。Java提供了多种机制来实现线程同步,本文将介绍Java中的同步机制及其应用...
    99+
    2023-11-09
    同步 numy 日志
  • 一文读懂flutter线程: 深入了解Flutter中的多线程编程
    深入了解Flutter中的多线程编程 前言一、为什么需要多线程?二、在Flutter中创建线程三、多线程的最佳实践四、Flutter中的多线程示例五、Flutter中的多线程错误处理六、Flu...
    99+
    2023-10-28
    flutter
  • Java编程语言中注意线程安全和同步是怎样的
    这篇文章给大家介绍Java编程语言中注意线程安全和同步是怎样的,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Java编程语言对于使用者来说是一个相当简单的编程语言。但是在使用的时候还是需要我们不断注意相关问题,下面我们...
    99+
    2023-06-17
  • Java线程同步:实现多线程协作的方法与技巧
    在Java中,多线程编程是非常常见的,但是在多线程编程中,线程之间的同步是一个非常关键的问题。同步是指多个线程在执行过程中,能够协调和互相配合,以达到正确的执行结果。否则,会出现数据不一致、死锁等问题,影响程序的正确性和性能。 本文将介绍...
    99+
    2023-11-09
    同步 numy 日志
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作