JavaEE多线程-线程的状态和安全问题

news2024/12/23 15:22:21

目录

  • 一、线程中的基本状态
  • 二、线程安全问题
  • 三、线程安全的标准类
  • 四、synchronized 关键字-监视器锁monitor lock
    • synchronized 的特性
  • 五、volatile 关键字

一、线程中的基本状态

NEW: 安排了工作, 还未开始行动, 就是创建了Thread对象, 但还没有执行start方法(内核里面还没有创建对应PCB), 这个状态是java内部的状态, 与操作系统中线程的状态没有关联.

RUNNABLE: 可工作的, 又可以分成正在工作中和即将开始工作(即正在CPU上执行的任务或者在就绪队列里随时可以去CPU上执行的).

BLOCKED: 线程正在等待锁释放而引起的阻塞状态(synchronized加锁).

WAITING: 线程正在等待等待唤醒而引起的阻塞状态(waitf方法使线程等待唤醒).

TIMED_WAITING: 在一段时间内处于阻塞状态, 通常是使用sleep或者join(带参数)方法引起.

TERMINATED:Thread对象还存在, 但是关联的线程已经工作完成了, 这个状态也是java内部的状态, 与操作系统中线程的状态没有关联.

线程的状态其实是一个枚举类型:Thread.State

二、线程安全问题

当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象时线程安全的。

举一个存在线程安全问题的例子:

class Counter {
    public int count = 0;

    public void add() {
        count++;
    }
}

public class Demo {
    public static void main(String[] args) {
        Counter counter = new Counter();

        // 搞两个线程, 两个线程分别针对 counter 来 调用 5w 次的 add 方法
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        // 启动线程
        t1.start();
        t2.start();

        // 等待两个线程结束
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打印最终的 count 值
        System.out.println("count = " + counter.count);
    }
}

在这里插入图片描述
为什么会出现这种情况呢?
原因还是线程的抢占式执行, 线程调度的顺序是随机的, 就造成线程间自增的指令集交叉, 导致运行时出现两次或者多次自增但值只会自增一次的情况, 导致得到的结果会偏小.

一次的自增操作本质上可以分成三步:
把内存中变量的值读取到CPU的寄存器中(load).
在寄存器中执行自增操作(add)
将寄存器的值保存至内存中(save)

如果是两个线程并发的执行count++, 此时就相当于两组 load, add, save进行执行, 此时不同的线程调度顺序就可能会产生一些结果上的差异.

线程加锁

为了解决由于 “抢占式执行” 所导致的线程安全问题, 我们可以针对当前所操作的对象进行加锁, 当一个线程拿到该对象的锁后, 就会将该对象锁起来, 其他线程如果需要执行该对象所限制任务时, 需要等待该线程执行完该对象这里的任务后才可以.

在Java中最常用的加锁操作就是使用synchronized关键字进行加锁.

方式一:使用synchronized关键字修饰普通方法, 这样会给方法所对在的对象加上一把锁.

class Counter {
    public int count = 0;

    synchronized public void add() {
        count++;
    }
}

方法二:使用synchronized关键字对代码段进行加锁, 需要显式指定加锁的对象.

class Counter {
    public int count = 0;

    public void add() {
        synchronized (this) {
            count++;
        }
    }
}

方法三:使用synchronized关键字修饰静态方法, 相当于对当前类的类对象进行加锁.

class Counter {
    public static int count = 0;

    synchronized public static void add() {
        count++;
    }
}

加锁本质上就是把并发变成了串行执行, 这样的话这里的自增操作其实和单线程是差不多的, 甚至上由于add方法, 要做的事情多了加锁和解锁的开销, 多线程完成自增可能比单线程的开销还要大, 那么多线程是不是就没用了呢? 其实不然, 对方法加锁后, 线程运行该方法才会加锁, 执行完该方法的操作后就会解锁, 此方法外的代码并没有受到限制, 这部分程序还是可以多线程并发执行的, 这样整体上多线程的执行效率还是要比单线程要高许多的.

产生线程安全问题的原因
1.线程是抢占式执行的,线程间的调度充满随机性。(线程不安全的根本原因)
2.多个线程对同一个变量进行修改操作。
3.原子性.
4.指令重排序和内存可见性问题

三、线程安全的标准类

Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, 又没有任何加锁措施.

  • ArrayList
  • LinkedList
  • HashMap
  • TreeMap
  • HashSet
  • TreeSet
  • StringBuilder
  • 但是还有一些是线程安全的. 使用了一些锁机制来控制.

  • Vector (不推荐使用)
  • HashTable (不推荐使用)
  • ConcurrentHashMap
  • StringBuffer
  • 还有的虽然没有加锁, 但是不涉及 “修改”, 仍然是线程安全的

  • String
  • 四、synchronized 关键字-监视器锁monitor lock

    synchronized 的特性

    1.互斥

    synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到
    同一个对象 synchronized 就会阻塞等待.
    进入 synchronized 修饰的代码块, 相当于 加锁
    退出 synchronized 修饰的代码块, 相当于 解锁
    在这里插入图片描述
    synchronized用的锁是存在Java对象头里的。
    理解 “阻塞等待”.

    针对每一把锁, 操作系统内部都维护了一个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝
    试进行加锁, 就加不上了, 就会阻塞等待, 一直等到之前的线程解锁之后, 由操作系统唤醒一个新的
    线程, 再来获取到这个锁.

    注意:

    上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 “唤醒”. 这
    也就是操作系统线程调度的一部分工作.

    假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B
    和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能
    获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则.

    synchronized的底层是使用操作系统的mutex lock实现的.

    2.刷新内存
    synchronized 的工作过程:

    1. 获得互斥锁
    2. 从主内存拷贝变量的最新副本到工作的内存
    3. 执行代码
    4. 将更改后的共享变量的值刷新到主内存
    5. 释放互斥锁
      所以 synchronized 也能保证内存可见性. 具体代码参见后面 volatile 部分

    3.可重入
    synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;
    在可重入锁的内部, 包含了 “线程持有者” 和 “计数器” 两个信息.
    如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取
    到锁, 并让计数器自增.
    解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)

    五、volatile 关键字

    volatile 能保证内存可见性
    在这里插入图片描述
    代码在写入 volatile 修饰的变量的时候,

    改变线程工作内存中volatile变量副本的值
    将改变后的副本的值从工作内存刷新到主内存
    代码在读取 volatile 修饰的变量的时候,

    从主内存中读取volatile变量的最新值到线程的工作内存中
    从工作内存中读取volatile变量的副本

    volatile 不保证原子性
    volatile 和 synchronized 有着本质的区别. synchronized 能够保证原子性, volatile 保证的是内存可见
    性.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/148686.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringSecurity(十一)【跨域】

十一、跨域 简介 跨域问题实际应用开发中的一个非常常见的需求&#xff0c;在 Spring 框架中对于跨域问题的处理方案有好几种&#xff0c;引入了 Spring Security 之后&#xff0c;跨域问题的处理方案又增加了 什么是 CORS CORS&#xff08;Cross-Origin Resource-Sharing&…

联合证券|重磅数据出炉,道指飙涨700点!美股新年首周“开门红”!

美股涨嗨了&#xff01; 当地时间1月6日&#xff0c;最新发布的美国12月非农工作陈述显现&#xff0c;美国工作市场终于呈现降温迹象&#xff0c;过去一年的激进加息成效初显。受此提振&#xff0c;美股三大股指高开高走&#xff0c;彻底改变前几日的跌势。从周k线看&#xff…

PCB结构和谐振(三)

PCB结构和谐振&#xff08;一&#xff09;PCB结构和谐振&#xff08;二&#xff09;仿真研究在本节中&#xff0c;我们首先对玻璃束的随机分布进行了简单的模拟研究。然后我们利用这些实验结论来简化常用的玻璃布3D结构。最后&#xff0c;这种简化的结构用于研究复杂层压板和两…

Java设计模式中原型模式是啥/浅克隆与深克隆又是什么/clone方法怎么回事

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 4.5 原型模式 4.5.1 概述 用已创建实例作为原型&#xff0c;通过复制该原型对象来创建一个和原型对象一样的新对象 4.5.2 结构 抽象原型类&#xff1a;规定具体…

Docker系列 深度使用nextcloud(九) 硬盘挂载

转自我的个人博客https://blognas.hwb0307.com&#xff0c;该文的内容更新仅在个人博客可见。欢迎关注&#xff01; 前言 基于《Docker系列 搭建个人云盘服务nextcloud》&#xff0c;相信无论是在有/无443端口的Linux机子里均可成功安装Nextcloud。值得一提的是&#xff0c;Ne…

02、做点准备工作 osg\openscenegraph源代码下载 C++三维视频融合实战系列(时空克隆)

首先&#xff0c;要有一点C编程基础&#xff0c;熟悉VS2013开发环境。 在开始实践之前&#xff0c;先要搭建号VS2013开发环境。 然后&#xff0c;建议电脑安装windows 10 64位操作系统。 接下来需要在以下地址下载开源代码&#xff1a; 1、osg下载 打开openscenegraph主页…

CSS权威指南(七)视觉格式化

文章目录1.盒模型2.元素的显示方式3.行内元素1.盒模型 不管是什么元素,CSS都假定每个元素会生成一个或多个矩形框,我们称之为元素。各元素框的中心是内容区域&#xff0c;四周有可选的内边距、边框、轮廓和外边距。默认情况下&#xff0c;内容区的背景出现在内边距范围内。外边…

5分钟搞懂用户态,内核态

5分钟搞懂用户态,内核态 1. 什么是用户态,内核态 用户态就是提供应用程序运行的空间&#xff0c;为了使应用程序访问到内核管理的资源例如CPU&#xff0c;内存&#xff0c;I/O。内核必须提供一组通用的访问接口&#xff0c;这些接口就叫系统调用。 用户态&#xff0c;内核态…

进制详解:二进制、八进制和十六进制

进制详解&#xff1a;二进制、八进制和十六进制 背景&#xff08;Contexts&#xff09; 我们平时使用的数字都是由 0~9 共十个数字组成的&#xff0c;例如 1、9、10、297、952 等&#xff0c;一个数字最多能表示九&#xff0c;如果要表示十、十一、二十九、一百等&#xff0c;…

机器学习笔记之深度信念网络(一)背景介绍与模型表示

机器学习笔记之深度信念网络——背景介绍与模型表示引言深度信念网络场景构建深度信念网络的联合概率分布引言 从本节开始&#xff0c;将介绍深度信念网络。 深度信念网络 深度信念网络(Deep Belief Network,DBN)是杰弗里辛顿(Geoffrey Hinton)于2006年提出的模型&#xff0…

Day853.WorkerThread模式 -Java 性能调优实战

WorkerThread模式 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于WorkerThread模式的内容。 Thread-Per-Message 模式&#xff0c;对应到现实世界&#xff0c;其实就是委托代办。这种分工模式如果用 Java Thread 实现&#xff0c;频繁地创建、销毁线程非常影响性能…

场景编程集锦 - 吉米的总统梦想

1. 场景描述 吉米是太平洋岛国一个贫苦家庭的孩子&#xff0c;他的梦想就是当总统&#xff0c;引领国家走向富强之路。 开学的第一堂课上&#xff0c;老师用白色的粉笔在黑板上写下了“我的梦想”&#xff0c;同学们都陷入了思考。大卫的梦想是当一名科学家&#xff0c;用奇思妙…

CSS初级教程(文本)【第六天】

文章目录【1】CSS 文本[字体颜色|背景色]【2】CSS 文本对齐【3】CSS 文字装饰【4】CSS 文本转换[大写或小写]【5】CSS 文字间距【6】CSS 文本阴影【7】所有 CSS 文本属性CSS上回学习链接 CSS初级教程 颜色【第一天】 CSS初级教程 背景【第二天】 CSS初级教程 边框【第三天】 CS…

Windows访问控制 -- SID

Windows访问控制是一个比较大的题目&#xff0c;因此计划用一系列的文章简单谈一下这个。本篇是开篇&#xff0c;介绍SID。 Windows访问控制定义 Windows访问控制的含义可以参考MSDN的描述&#xff1a;Access control refers to security features that control who can acce…

Java集合容器介绍

Java 容器分为 Collection 和 Map 两大类&#xff0c;其下又有很多子类&#xff0c;如下所示&#xff1a; Collection接口&#xff1a;单列数据&#xff0c;定义了存取一组对象的方法的集合 1、List&#xff1a;元素有序(指的是存储时&#xff0c;与存放顺序保持一致)、可重复的…

【Docker】(四)使用volume持久化Docker容器中的Redis数据

1.前言 本系列文章记录了从0开始学习Docker的过程&#xff0c;Docker系列历史文章&#xff1a; &#xff08;一&#xff09;基本概念与安装使用 &#xff08;二&#xff09;如何使用Docker发布一个SpringBoot服务 &#xff08;三&#xff09;使用registry远程镜像仓库管理镜像…

[ 数据结构 ] 赫夫曼编码--------数据、文件压缩解压

0 引出 如上图:给定字符串按定长编码处理,最终对应二进制长度为359 思考:如何压缩,将359有效降低? ----回顾:赫夫曼树 1 数据压缩 拿到数据(字符串)的第一反应,虽然知道应该也像上面一样转为字节数组,但就不知道该怎么办了?统计数组中各字节使用的次数,将次数作为权值,字节…

2023.1.8 学习周报

文章目录摘要文献阅读1.题目2.摘要3.介绍4.论文主要贡献5.相关工作5.1 序列感知的推荐系统5.2 神经注意模型6.模型&#xff1a;ATTREC6.1 序列推荐6.2 基于Self-Attention的用户短期兴趣建模6.3 用户长期兴趣建模6.4 模型学习7.实验7.1 数据集7.2 评估指标7.3 模型比较7.4 实验…

SSO单点登录实例详解(前端传Code授权登录)

什么是 SSO&#xff08;单点登录&#xff09; SSO 英文全称 Single Sign On&#xff0c;单点登录。SSO 是在多个应用系统中&#xff0c;用户只需要登录一次就可以访问所有相互信任的应用系统。 单点登录流程 单点登录大致流程如下所示&#xff1a; 单点登录详细流程&#x…

【自学C++】C++变量初始化

C变量初始化 C变量初始化教程 变量 的初始化就是在定义变量的同时&#xff0c;给变量设置一个初始值&#xff0c;在 C 中&#xff0c;如果定义变量没有初始化&#xff0c;那么变量有可能会被赋值也有可能不会赋值。 如果是定义的 全局变量 或者 静态变量&#xff0c;未初始化…