我的编程空间,编程开发者的网络收藏夹
学习永远不晚

浅谈java线程状态与线程安全解析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

浅谈java线程状态与线程安全解析

1.线程的几种状态

1.1 线程的状态

以下就是我们线程所有的状态和意义:

NEW已经创建Thread但未创建线程
RUNNABLE可工作的. 又可以分成正在工作中和即将开始工作
BLOCKED等待锁(阻塞状态)
WAITING调用wati方法(阻塞状态)
TIMED_WAITING调用sleep方法(阻塞状态)
TERMINATED系统线程执行完毕已销毁,但Thread还存在

注意:

BLOCKED 表示等待获取锁, WAITING 和 TIMED_WAITING 表示等待其他线程发来通知.

TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在无限等待唤醒 

1.2 线程状态的转移 

各线程之间的转移关系可以简化成下图:

 关于yield方法:

在多线程中我们存在一个yield方法可以让线程在就绪队列中重新”排队“,不改变线程状态。相当于你去帮别人排队,但是轮到你了那个人还没回来,你就就让原本排在你后面的人换到你的位置上,但你仍然处于排队状态。这种”大公无私“的行为可以类比到我们的yield方法帮助我们理解。

public class Demo{
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() { while (true) {
                System.out.println("张三");
                // 先注释掉, 再放开
                //Thread.yield();
            }
            }
        }, "t1");
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("李四");
                }
            }
        }, "t2");
        t2.start();
 
    }
}

 可以看到:

1. 不使用 yield 的时候, 张三李四大概五五开

2. 使用 yield 时, 张三的数量远远少于李四

结论: yield 不改变线程的状态, 但是会重新去排队.

2.有关线程安全问题

2.1 一个简单的例子

// 创建两个线程, 让这俩线程同时并发的对一个变量, 自增 5w 次. 最终预期能够一共自增 10w 次.
class Counter {
    // 用来保存计数的变量
    public int count;
 
    public void increase() {
        count++;
    }
}
 
public class Demo {
    // 这个实例用来进行累加.
    // public static Counter counter = new Counter();
 
    public static void main(String[] args) {
        Counter counter = new Counter();
 
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
 
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count: " + counter.count);
    }
}

大家先看到以上的代码,意思很简单,用两个线程对同一个变量进行自增操作,运行的结果如下 

看起来不太对,我们再试一次

结果有了变化,但仍然不是我们想要的结果,是什么导致了5w+5w<10w呢?其中一个原因就是线程的随机调度和改操作不具有原子性。 这些概念我们下面会详细讲,这里我们先简单了解一下。

首先我们的自增操作在cpu内其实分为三步:

1.LOAD:cpu从内存中读取数据到寄存器

2.ADD:在寄存器内实现自增

3.SAVE:将寄存器的数据写回内存中

而我们已经知道cpu对于线程调度我们可以理解为是随机的,所以会有很多种可能,比如下图

其中纵轴代表运行时间,这里我们可以看到两个线程相当于互不影响,线程1完成自增操作后又将数据写回内存由线程2再去操作,这种情况下是没有问题的。但是也可能是下面的一种情况

 此时线程1还没有将自增后的数据写回内存而线程2就已经将要修改的数据读入了寄存器,此时相当于线程2读到了那个还未自增的数据,相当于两个线程对同一个数进行了自增,所以此时相当于只自增了一次。其实情况还有很多,这里我们仅举例比较经典的例子。所以这也能够解释为什么结果大于5w而小于10w了。

2.2 造成线程不安全的原因

2.2.1 操作系统的随机调度/抢占式运行

这种是操作系统内核就已经决定的,我们无能为力。类似于我们上一个例子,就是因为线程的随机调度和操作不具有原子性造成的。 

2.2.2 操作不具有原子性 

什么是原子性

我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入 房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性 的。转换成代码我们可以理解成只具有一条指令的操作。

当然这个问题我们可以通过加锁操作解决(以后会提到)。

一条 java 语句不一定是原子的,也不一定只是一条指令,比如我们上面提到的自增操作。

不保证原子性会给多线程带来什么问题

如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。 这点也和线程的抢占式调度密切相关. 如果线程不是 "抢占" 的, 就算没有原子性, 也问题不大.

2.2.3 多个线程修改同一个变量

1.一个线程修改变量没事

2.多个线程同时一个变量也没事

3.多个线程同时修改不同变量也没有问题

唯独需要注意多个线程修改同一个变量,如果不加以处理可能会造成我们之前讲到的例子的问题 

2.2.4 内存可见性问题 

jvm中规定了java的内存模型

线程之间的共享变量存在 主内存 (Main Memory).

每一个线程都有自己的 "工作内存" (Working Memory) .

当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据.

当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存. 

正是因为这种机制,所以可能会出现下面的问题:

由于每个线程有自己的工作内存, 这些工作内存中的内容相当于同一个共享变量的 "副本". 此时修改线程 1 的工作内存中的值, 线程2 的工作内存不一定会及时变化.通俗的讲就是 线程1针对工作内容修改了数据,而线程2此时并不一定能够及时同步修改的数据,所以可能会引发各种问题。

2.2.5 指令重排序 

所谓指令重排序是指jvm针对我们的代码,可能会在保证逻辑不变的情况下去调整指令执行的顺序以达到运行效率更高的效果。这种情况在单线程的情况下可以很好实现,而在多线程的情况下就可能会出现bug,导致程序逻辑改变。比如对于下面这行代码:

Test t=new Test();

它其实总共有三个步骤:

1.创建内存空间

2.往这个内存空间构造一个对象

3.将这个内存引用赋给t 

在单线程的情况下2,3互换并不会有上面影响,但假如在多线程情况下我们按1,3,2来执行,当执行到3时t为非null,此时线程2读取t,但是却发现是一个无效对象。 

到此这篇关于浅谈java线程状态与线程安全解析的文章就介绍到这了,更多相关java线程状态与线程安全内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

浅谈java线程状态与线程安全解析

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

下载Word文档

猜你喜欢

浅谈java线程状态与线程安全解析

本文主要介绍了浅谈java线程状态与线程安全解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-02-03

Java线程安全与非线程安全解析

ArrayList和Vector有什么区别?HashMap和HashTable有什么区别?StringBuilder和StringBuffer有什么区别?这些都是Java面试中常见的基础问题。面对这样的问题,回答是:ArrayList是非线
2023-05-31

Java线程安全状态的示例分析

这篇文章主要为大家展示了“Java线程安全状态的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Java线程安全状态的示例分析”这篇文章吧。一、观察线程的所有状态线程的状态是一个枚举类型
2023-06-29

浅谈线程的几种可用状态

1. 新建( new ):新创建了一个线程对象。
2023-05-31

详解Java的线程状态

本文主要为大家详细介绍一下Java的线程状态,文中的示例代码讲解详细,对我们学习有一定的帮助,感兴趣的小伙伴可以跟随小编学习一下
2022-11-13

Java多线程之线程状态详解之一

我们已经知道Java中线程一共有6种状态,在正式开始介绍之前,先给大家介绍一个监控线程状态的工具,也是JDK自带的工具。通过这个工具,我们可以更清晰的看到线程此时此刻所处的状态是什么。

Java线程安全中的原子性浅析

这篇文章主要介绍了Java线程安全中的原子性,原子性是指一条线程在执行一系列程序指令操作时,该线程不可中断。一旦出现中断,那么就可能会导致程序执行前后的结果不一致
2023-02-21

Java中线程状态+线程安全问题+synchronized的用法是什么

这篇文章主要介绍了Java中线程状态+线程安全问题+synchronized的用法是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java中线程状态+线程安全问题+synchronized的用法是什么文章都
2023-06-29

谈谈你对Java线程5种状态流转原理的理解

举个通俗一点的例子来解释五种状态,比如我们平时去商场上厕所,准备去上厕所就是新建状态(new),上厕所要排队,排队就是就绪状态(Runnable),有坑位了,轮到你了,蹲坑就是运行状态(Running),蹲完坑发现没有手纸,需要等待其他人送

Java线程安全中的有序性浅析

这篇文章主要介绍了Java线程安全中的有序性,在开发中,我们通常按照从上到下的顺序编写程序指令,并且希望cpu和编译器按照我们预先编写的顺序去执。但往往cpu和编译器为了提高性能、优化指令的执行顺序,会将我们编写好的程序指令进行重排序
2023-02-21

Java线程安全与不安全实例分析

本篇内容主要讲解“Java线程安全与不安全实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java线程安全与不安全实例分析”吧!当我们查看JDK API的时候,总会发现一些类说明写着,线程
2023-06-17

Java多线程 - 线程安全和线程同步解决线程安全问题

文章目录 线程安全问题线程同步方式一: 同步代码块方式二: 同步方法方式三: Lock锁 线程安全问题 线程安全问题指的是: 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。 举例:
2023-08-20

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录