iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java中线程状态+线程安全问题+synchronized的用法详解
  • 402
分享到

Java中线程状态+线程安全问题+synchronized的用法详解

2024-04-02 19:04:59 402人浏览 薄情痞子

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

摘要

目录java中的线程状态?线程安全问题案例分析?多线程对同一变量进行写操作?内存可见性问题?指令重排序问题?synchronized的用法?synchronized起作用的本质?修饰

java中的线程状态?

操作系统层面,一个线程就两个状态:就绪和阻塞状态.

但是java中为了在线程阻塞时能够更快速的知晓一个线程阻塞的原因,又将阻塞的状态进行了细化.

  • NEW:线程对象已经创建好了,但是系统层面的线程还没创建好,或者说线程对象还没调用start()
  • TERMINATED:系统中的线程已经销毁,但是代码中的线程对象还在,也就是run()跑完了,Thread对象还在
  • RUNNABLE:线程位于就绪队列,随时都有可能被cpu调度执行
  • TIMED_WaiTING:线程执行过程中,线程对象调用了sleep(),进入阻塞,休眠时间到了,就会回到就绪队列
  • BLOCKED:有一个线程将一个对象上(synchronized)之后,另一个线程也想给这个对象上锁,就会陷入BLOCKED状态,只有第一个线程将锁对象解锁了,后一个线程才有可能给这个对象进行上锁.
  • WAITING:搭配synchronized进行使用wait(),一旦一个线程调用了wait(),会先将所对象解锁,等到另一个线程进行notify(),之后wait中的线程才会被唤醒,当然也可以在wait()中设置一个最长等待时间,防止出现死等.

线程安全问题案例分析?

多线程对同一变量进行写操作?

  1. 概念:一串代码什么时候叫作有线程安全问题呢?首先线程安全问题的罪恶之源是,多线程并发执行的时候,会有抢占式执行的现象,这里的抢占式执行,执行的是机器指令!那一串代码什么时候叫作有线程安全问题呢?多线程并发时,不管若干个线程怎么去抢占式执行他们的代码,都不会影响最终结果,就叫作线程安全,但是由于抢占式执行,出现了和预期不一样的结果,就叫作有线程安全问题,出bug了!
  2. 典型案例:使用两个线程对同一个数进行自增操作10w次:
public class Demo1 {
    private static int count=0;
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            for(int i=0;i<50000;i++){
                count++;
            }
        });
        t1.start();
        Thread t2=new Thread(()->{
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }
}
//打印结果:68994

显然预期结果是10w,但算出来就是6w多,这就是出现了线程安全问题.

分析原因:

仅针对每个线程的堆count进行自增的操作:首先要明白,进行一次自增的机器指令有三步:从主内存中把count值拿到cpu寄存器中->把寄存器中的count值进行自增1->把寄存器中的count值刷新到主内存中,我们姑且把这三步叫作:load->add->save

我们假设就是在一个cpu上(画两个cpu好表示)并发执行两组指令(就不会出现同时load这样的情况了):

如出现上图的情况:

观察发现:两个线程都是执行了一次count++,但是两次++的结果却不如意,相当于只进行了一次自增,上述就是出现了线程安全问题了.

并且我们可以预测出上述代码的结果范围:5w-10w之间!,为什么呢?

上面两张图表示的是出现线程安全问题的情况,表现的结果就是两次加加当一次去用了,如果两个线程一直处于这样的状态(也是最坏的状态了),可不就是计算结果就是5w咯,那如果两个线程一直是一个线程完整的执行完load-add-save之后,另一个线程再去执行这样的操作,那就串行式执行了,可不就是10w咯.

3.针对上述案例如何去解决呢?

案例最后也提到了,只要能够实现串行式执行,就能保证结果的正确性,那java确实有这样的功能供我们使用,即synchronized关键字的使用.

也就是说:cpu1执行load之前先给锁对象进行加锁,save之后再进行解锁,cpu2此时才能去给那个对象进行上锁,并进行一系列的操作.此时也就是保证了load-add-save的原子性,使得这三个步骤要么就别执行,执行就一口气执行完.

那你可能会提问,那这样和只用一个main线程去计算自增10w次有什么区别,创建多线程还有什么意义呢?

意义很大,因为我们创建的线程很多时候不仅仅只是一个操作,光针对自增我们可以通过加锁防止出现线程安全问题,但是各线程的其他操作要是不涉及线程安全问题那就可以并发了呀,那此时不就大大提升了执行效率咯.

4.具体如何加锁呢?

此处先只说一种加锁方式,先把上述案例的问题给解决了再说.

使用关键字synchronized,此处使用的是给普通方法加synchronized修饰的方法(除此之外,synchronized还可以修饰代码块和静态方法)

class Counter{
    private int count;
    synchronized public void increase(){
        this.count++;
    }
    public int getCount(){
        return this.count;
    }
}
public class Demo2 {
    private static int num=50000;
    public static void main(String[] args) {
        Counter counter=new Counter();//此时对象中的count值默认就是0
        Thread t1=new Thread(()->{
            for (int i = 0; i < num; i++) {
                counter.increase();
            }
        });
        t1.start();

        Thread t2=new Thread(()->{
            for (int i = 0; i < num; i++) {
                counter.increase();
            }
        });
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(counter.getCount());
    }
}//打印10W

内存可见性问题?

首先说明:这是有编译器优化导致的,其次要知道cpu读取变量时:先从主内存将变量的值存至缓存或者寄存器中,cpu计算时再在寄存器中读取这个值.

当某线程频繁的从内存中读取一个不变的变量时,编译器将会把从内存获取变量的值直接优化成从寄存器直接获取.之所以这样优化,是因为,cpu从主内存中读取一个变量比在缓存或者寄存器中读取一个变量的值慢成千上万倍,如果每每在内存中读到的都是同一个值,既然缓存里头已经有这个值了,干嘛还大费周折再去主内存中进行获取呢,直接从缓存中直接读取就可以了,可提升效率.

但是:一旦一个线程被优化成上述的情况,那如果有另一个线程把内存中的值修改了,我被优化的线程还傻乎乎的手里拿着修改之前的值呢,或者内存中的变量值被修改了,被优化的线程此时已经感应不到了.

具体而言:

public class Demo3 {
    private static boolean flag=false;
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            while(!flag){
                System.out.println("我是优化完之后直接读取寄存器中的变量值才打印的哦!");
            }
        });
        t1.start();

        flag=true;
        System.out.println("我已经在主线程中修改了标志位");
    }
}

运行上述代码之后,程序并不会终止,而是一直在那打印t1线程中的打印语句.

如何解决上述问题:

引入关键字volatile:防止内存可见性问题,修饰一个变量,那某线程想获取该变量的值的时候,只能去主内存中获取,其次它还可以防止指令重排序,指令重排问题会在线程安全的单例模式(懒汉)进行介绍.具体:

public class Demo3 {
    private static volatile boolean flag=false;
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            while(!flag){
                System.out.println("我是优化完之后直接读取寄存器中的变量值才打印的哦!");
            }
        });
        t1.start();

        try {
            Thread.sleep(1);//主线程给t1留有充足的时间先跑起来
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag=true;
        System.out.println("我已经在主线程中修改了标志位");
    }
}
//打印若干t1中的打印语句之后,主线程main中修改标志位之后,可以终止t1

注意:上述优化现象只会出现在频繁读的情况,如果不是频繁读,就不会出现那样的优化.

指令重排序问题?

生活案例:买菜

如果是傻乎乎的按照菜单从上到下的去买菜,从路线图可以看出,不必要的路是真的没少走.

如果执行代码时,编译器认为某些个代码调整一下顺序并不会影响结果,那代码的执行顺序就会被调整,就比如可以把上面买菜的顺序调整成:黄瓜->萝卜->青菜->茄子

单线程这样的指令重排一般不会出现问题,但是多线程并发时,还这样优化,就容易出现问题

针对这样的问题,如果是针对一个变量,我们可以使用volatile修饰,如果是针对代码块,我们可以使用synchronized.

synchronized的用法?

  • synchronized起作用的本质
  • 修饰普通方法
  • 修饰静态方法
  • 修饰代码块

synchronized起作用的本质?

因为我们知道java中所有类都继承了Object,所以所有类都包含了Object的部分,我们可以称这继承的部分是"对象头",使用synchronized进行对象头中的标志位的修改,就可以做到一个对象的锁一个时刻只能被一个线程所持有,其他线程此时不可抢占.这样的设置,就好像把一个对象给锁住了一样.

修饰普通方法?

如前述两个线程给同一个count进行自增的案例.不再赘述.此时的所对象就是Counter对象

修饰静态方法⚡️

与普通方法类似.只不过这个方法可以类名直接调用.

修饰代码块?

首先修饰代码块需要执行锁对象是谁,所以这里可以分为三类,一个是修饰普通方法的方法体这个代码块的写法,其次是修饰静态方法方法体的写法,最后可以单独写一个Object的对象,来对这个Object对象进行上锁.

class Counter{
    private int count;
    public void increase(){
        synchronized(this){
            count++;
        }
    }
    public int getCount(){
        return this.count;
    }
}
class Counter{
    private static int count;
    public static void increase(){
        synchronized(Counter.class){//注意这里锁的是类对象哦
            count++;
        }
    }
    public int getCount(){
        return this.count;
    }
}
class Counter{
    private static int count;
    private static Object locker=new Object();
    public static void increase(){
        synchronized(locker){
            count++;
        }
    }
    public int getCount(){
        return this.count;
    }
}

注意:java中这种随手拿一个对象就能上锁的用法,是java中一种很有特色的用法,在别的语言中,都是有专门的锁对象的.

Conclusion?

java中的线程状态,以及如何区分线程安全问题 罪恶之源是抢占式执行多线程对同一个变量进行修改,多线程只读一个变量是没有线程安全问题的修改操作是非原子性的内存可见性引起的线程安全问题指令重排序引起的线程安全问题 synchronized的本质和用法

1.java中的线程状态,以及如何区分
2.线程安全问题

  • 罪恶之源是抢占式执行
  • 多线程对同一个变量进行修改,多线程只读一个变量是没有线程安全问题的
  • 修改操作是非原子性的
  • 内存可见性引起的线程安全问题
  • 指令重排序引起的线程安全问题

3.synchronized的本质和用法

到此这篇关于Java中线程状态+线程安全问题+synchronized的用法详解的文章就介绍到这了,更多相关java线程安全synchronized用法内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java中线程状态+线程安全问题+synchronized的用法详解

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

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

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

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

下载Word文档
猜你喜欢
  • Java中线程状态+线程安全问题+synchronized的用法详解
    目录java中的线程状态线程安全问题案例分析多线程对同一变量进行写操作内存可见性问题指令重排序问题synchronized的用法synchronized起作用的本质修饰普通方法修饰静...
    99+
    2022-11-13
  • Java中线程状态+线程安全问题+synchronized的用法是什么
    这篇文章主要介绍了Java中线程状态+线程安全问题+synchronized的用法是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java中线程状态+线程安全问题+synchronized的用法是什么文章都...
    99+
    2023-06-29
  • Java线程安全状态专题解析
    一、观察线程的所有状态 线程的状态是一个枚举类型 Thread.State public static void main(String[] args) { f...
    99+
    2022-11-13
  • Java多线程之线程安全问题详解
    目录1. 什么是线程安全和线程不安全?2. 自增运算为什么不是线程安全的?3. 临界区资源和竞态条件总结:面试题: 什么是线程安全和线程不安全?自增运算是不是线程安全的?如何保证多线...
    99+
    2022-11-13
  • 浅谈java线程状态与线程安全解析
    目录1.线程的几种状态1.1 线程的状态1.2 线程状态的转移 2.有关线程安全问题2.1 一个简单的例子2.2 造成线程不安全的原因1.线程的几种状态 1.1 线程的状态...
    99+
    2023-02-03
    java线程状态 java线程安全
  • Java使用线程同步解决线程安全问题详解
    第一种方法:同步代码块: 作用:把出现线程安全的核心代码上锁 原理:每次只能一个线程进入,执行完毕后自行解锁,其他线程才能进来执行 锁对象要求:理论上,锁对象只要对于当前同时执行的线...
    99+
    2022-11-13
  • 关于java中线程安全问题详解
    目录一、什么时候数据在多线程并发的环境下会存在安全问题?二、怎么解决线程安全问题?三、银行 取钱/存钱 案例为什么会出现线程安全问题四、总结 一、什么时候数据在多线程并发的环境下会存...
    99+
    2022-11-12
  • 详解Java的线程状态
    Java的每个线程都具有自己的状态,Thread类中成员变量threadStatus存储了线程的状态: private volatile int threadStatus = 0; ...
    99+
    2022-11-13
    Java线程状态 Java线程
  • Java多线程之线程安全问题详情
    目录1.线程安全概述1.1什么是线程安全问题1.2一个存在线程安全问题的程序2.线程加锁与线程不安全的原因2.1案例分析2.2线程加锁2.2.1什么是加锁2.2.2如何加锁2.2.3...
    99+
    2022-11-13
  • Java多线程 - 线程安全和线程同步解决线程安全问题
    文章目录 线程安全问题线程同步方式一: 同步代码块方式二: 同步方法方式三: Lock锁 线程安全问题 线程安全问题指的是: 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。 举例:...
    99+
    2023-08-20
    java 安全 jvm
  • Java中线程安全问题
    目录一.线程不安全二.那些情况导致了线程不安全?三.Java中解决线程不安全的方案1.volatile“轻量级”解决线程不安全2.synchronized自动加锁四.公平锁与非公平锁...
    99+
    2022-11-12
  • Java多线程之线程状态的迁移详解
    目录一、六种状态二、状态迁移图三、线程状态模拟总结一、六种状态 java.lang.Thread 的状态分为以下 6 种,它们以枚举的形式,封装在了Thread类内部: NEW:表...
    99+
    2022-11-12
  • Java多线程之线程安全问题怎么解决
    本篇内容主要讲解“Java多线程之线程安全问题怎么解决”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java多线程之线程安全问题怎么解决”吧!1.线程安全概述1.1什么是线程安全问题首先我们需要...
    99+
    2023-06-30
  • SimpleDateFormat线程安全问题排查详解
    目录一. 问题现象二. 原因排查三. 原因分析四. 解决方案一. 问题现象 运营部门反馈使用小程序配置的拉新现金红包活动二维码,在扫码后跳转至404页面。 二. 原因排查 首先,检查...
    99+
    2022-11-13
    SimpleDateFormat线程安全排查 SimpleDateFormat线程安全
  • Java线程安全状态的示例分析
    这篇文章主要为大家展示了“Java线程安全状态的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Java线程安全状态的示例分析”这篇文章吧。一、观察线程的所有状态线程的状态是一个枚举类型 ...
    99+
    2023-06-29
  • Java解析线程的几种状态详解
    目录1. 线程的5种状态2. Java线程的6种状态3. Java线程状态的转换总结1. 线程的5种状态 从操作系统层面上,任何线程一般都具有五种状态,即创建、就绪、运行、阻塞、终止...
    99+
    2022-11-13
  • Java多线程中线程安全问题的示例分析
    这篇文章主要介绍了Java多线程中线程安全问题的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1. 什么是线程安全和线程不安全?什么是线程安全呢?当多个线程并发访问某...
    99+
    2023-06-29
  • Java线程安全问题的解决方案
    目录线程安全问题演示解决线程安全问题1.原子类AtomicInteger2.加锁排队执行2.1 同步锁synchronized2.2 可重入锁ReentrantLock3.线程本地变...
    99+
    2022-11-13
  • Java中线程安全问题该如何理解
    这期内容当中小编将会给大家带来有关Java中线程安全问题该如何理解,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。线程安全问题是一个比较高深的问题,是很多程序员比较难掌握的一个技术难点,如果一个程序员对线程...
    99+
    2023-06-17
  • java treemap线程安全问题怎么解决
    要解决Java TreeMap的线程安全问题,有以下几种方法:1. 使用Collections.synchronizedMap()方...
    99+
    2023-10-20
    java
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作