【多线程】线程安全(重点)

news2024/11/17 9:32:43

文章目录

    • 1. 观察线程不安全
      • 1.1 示例1
      • 1.2 示例2
    • 2. 线程不安全的原因
      • 2.1 修改共享数据
      • 2.2 原子性
      • 2.3 可见性
      • 2.4 顺序性
    • 3. synchronized同步方法
      • 3.1 synchronized特性
        • 3.1.1 互斥
        • 3.1.2 刷新内存
        • 3.1.3 可重入
      • 3.2 synchronized使用
        • 3.2.1 直接修饰普通方法
        • 3.2.2 修饰静态方法
        • 3.2.3 修饰代码块
      • 3.3 使示例1安全
    • 4. volatile关键字
      • 4.1 概念
      • 4.2 使示例2安全
    • 5. synchronized与volatile比较

1. 观察线程不安全

1.1 示例1

package Test;

//观察线程不安全
public class Test333 {
    public static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        //线程t1对count加10000
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                add();
            }
        });
        //线程t2对count加10000
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                add();
            }
        });
        //启动两个线程
        t1.start();
        t2.start();
        Thread.sleep(1000);
        //输出应该为20000
        System.out.println(count);
    }
    public static void add(){
        count++;
    }
}

运行两次结果:
1
2

根据运行结果可以看出,每次运行结果并不相同,但是并没有一个是正确答案,这种情况便是线程不安全。多线程环境下运行代码结果和单线程环境下结果不相同,没有达到我们预期的结果,那么这个线程就是“非线程安全”。

1.2 示例2

package Test;

import java.util.Scanner;

public class Counter {
    public int flag = 0;

    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            while (counter.flag == 0) {
            // do nothing
            }
            System.out.println("循环结束!");
        });
        Thread t2 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入一个整数:");
            counter.flag = scanner.nextInt();
        });
        t1.start();
        t2.start();
    }
}

运行结果:
2

并不会结束,还在运行。
这种多线程运行结果,和我们预想的结果也不同,那么这也是非线程安全。

2. 线程不安全的原因

2.1 修改共享数据

通过上面的两次例子,我们变可以看出,他们都有一个共同的地方,就是两个线程共享数据。
那么,当一个线程修改数据途中,另一个线程启动也会修改这个数据,这样就会造成结果与预想不符,造成非线程安全。

2.2 原子性

线程原子性指一个操作是不可在分的,不可中断的,在整个操作执行完毕前,不会有其他线程对它干扰。如果一个操作是原子性的,那么就不会发生造成竞态条件出现。
相当于一个没锁的读书亭,一个人进行读书,因为读书亭没锁其他人可以随意进,从而会干扰到第一个人。而原子性就相当于给读书亭加上锁,第一个人进去时锁上门,那么就不会被打扰。
而1.1中例子,执行一个count++语句时,它并不是原子性的,分为三步:1. 读取变量count的值到CPU的寄存器中
2. 进行值加1
3. 最后将新值写回count中
N++
当t1线程读取到数据count = 0,进行++运算中,新的值还没写回count中,t2线程也运行,最后写回count,count = 1,而不是2,造成非线程安全。

2.3 可见性

线程可见性指当一个线程修改共享资源时,其他线程能够及时知道最新值,从而不会发生线程安全事故。
示例2就是因此出现bug。

2.4 顺序性

线程顺序性就是代码重排序指代码重排序是指编译器、处理器为了提高程序性能而对程序中的指令进行重新排序的过程。在单线程环境下,重排序不会影响程序最终的执行结果,因为编译器和处理器必须保证单线程程序的语义正确。但是,在多线程环境下,重排序会对程序的并发执行产生影响,如果不加以控制,可能会导致程序出现错误。

3. synchronized同步方法

如何解决示例中的问题,是线程达到我们预期的结果,使线程安全,那么synchronized这个关键字便可以起到重要作用。

3.1 synchronized特性

3.1.1 互斥

synchronized具有互斥效果,当一个线程执行synchronized修饰对象时,其他线程在执行这个对象,便会堵塞,只有第一个线程执行结束,其他线程才可以执行这个对象。

  • 进入synchronized修饰的代码块,相当于加锁
  • 出相当于解锁

3.1.2 刷新内存

synchronized的工作过程:

  1. 获得互斥锁
  2. 从主内存拷贝变量的最新副本到工作的内存
  3. 执行代码
  4. 将更改后的共享变量的值刷新到主内存
  5. 释放互斥锁

3.1.3 可重入

synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题。

// 第一次加锁, 加锁成功
lock();
// 第二次加锁, 锁已经被占用, 阻塞等待.
lock();

3.2 synchronized使用

3.2.1 直接修饰普通方法

//锁的 SynchronizedDemo 对象
public class SynchronizedDemo {
  public synchronized void methond() {
 }
}

3.2.2 修饰静态方法

//锁的 SynchronizedDemo 类的对象
public class SynchronizedDemo {
  public synchronized static void method() {
 }
}

3.2.3 修饰代码块

//明确指定锁哪个对象
public class SynchronizedDemo {
  public void method() {
    synchronized (this) {
     
   }
 }

3.3 使示例1安全

示例1只需对add()方法加锁:

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

那么,再次运行:
安全1

4. volatile关键字

4.1 概念

volatile只可以修饰变量,而被修饰的变量将具有可见性。 加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了。
但是volatile不保证原子性,而synchronized也可以保证可见性。

4.2 使示例2安全

只需加上volatile:

    public volatile int flag = 0;

那么,安全:
安全2

5. synchronized与volatile比较

  1. volatile是线程同步的轻量级实现,性能较好,但只可以修饰变量,而synchronized还可以修饰方法,代码块。开发中后者应用交多。
  2. 多线程访问volatile不会堵塞,synchronized会发生堵塞。
  3. volatile只可以保证可见性,不能保证原子性,synchronized都可。

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

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

相关文章

开源照片管理服务LibrePhotos

本文是为了解决网友 赵云遇到的问题&#xff0c;顺便折腾的。虽然软件跑起来了&#xff0c;但是他遇到的问题&#xff0c;超出了老苏的认知。当然最终问题还是得到了解决&#xff0c;不过与 LibrePhotos 无关&#xff1b; 什么是 LibrePhotos ? LibrePhotos 是一个自托管的开源…

uniapp微信小程序用户隐私保护

使用wx.requirePrivacyAuthorize实现微信小程序用户隐私保护。 一、前言 微信小程序官方出了一个公告《关于小程序隐私保护指引设置的公告》。不整的话&#xff0c;后果很多授权无法使用&#xff0c;详见《小程序用户隐私保护指引内容介绍》 。 二、隐私相关设置 1、在 微信…

基于Laravel通用型内容建站企业官网系统源码 可免费商用

是一个基于 Laravel 企业内容建站系统。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;免费且不限制商业使用 2023年08月23日增加了以下12个特性&#xff1a; [新功能] 手机端Banner支持…

视频监控人员行为识别算法

视频监控人员行为识别算法通过opencvpython网络模型框架算法&#xff0c;视频监控人员行为识别算法可以识别和判断员工的行为是否符合规范要求&#xff0c;一旦发现不符合规定的行为&#xff0c;视频监控人员行为识别算法将自动发送告警信息。OpenCV的全称是Open Source Comput…

Java8实战-总结18

Java8实战-总结18 使用流筛选和切片用谓词筛选筛选各异的元素截短流跳过元素 使用流 流让你从外部迭代转向内部迭代。这样&#xff0c;就用不着写下面这样的代码来显式地管理数据集合的迭代(外部迭代)了&#xff1a; List<Dish> vegetarianDishes new ArrayList<>…

​​​​​​​嵌入式学习笔记(8)ARM汇编伪指令

伪指令的意义 伪指令不是指令&#xff0c;伪指令和指令的根本区别是经过汇编后不会生成机器码。 伪指令的意义在于指导汇编过程。 伪指令是和具体的汇编器有关的&#xff0c;我们使用gnu工具链&#xff0c;因此学习gnu下的汇编伪指令 gnu汇编中的一些符号 用来做注释。 : …

react利用wangEditor写评论和@功能

先引入wangeditor写评论功能 import React, { useEffect, useState, useRef, forwardRef, useImperativeHandle } from react; import wangeditor/editor/dist/css/style.css; import { Editor, Toolbar } from wangeditor/editor-for-react; import { Button, Card, Col, For…

IPv6网络实验:地址自动生成与全球单播通信探索

文章目录 一、实验背景与目的二、实验拓扑三、实验需求四、实验解法1. 在R1和PC3上开启IPv6链路本地地址自动生成&#xff0c;测试是否能够使用链路本地地址互通2. 为R1配置全球单播地址2001::1/64&#xff0c;使PC3能够自动生成与R1同一网段的IPv6地址3. 测试R1和PC3是否能够使…

动力学约束下的运动规划算法——Hybrid A*算法(附程序实现及详细解释)

前言&#xff08;推荐读一下&#xff09; 本文主要介绍动力学约束下的运动规划算法中非常经典的Hybrid A*算法&#xff0c;大致分为三部分&#xff0c;第一部分是在传统A * 算法的基础上&#xff0c;对Hybrid A * 算法的原理、流程进行理论介绍。第二部分是详细分析 MotionPl…

[C++]vector使用和模拟实现

&#x1f941;作者&#xff1a; 华丞臧 &#x1f4d5;​​​​专栏&#xff1a;【C】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449;LeetCode 文章目录 一、vec…

什么是Flex容器和Flex项目(Flex Container and Flex Item)?它们之间有什么关系?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ Flex容器和Flex项目⭐ Flex容器⭐ Flex项目⭐ 关系⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为…

OpenCV: cv2.findContours - ValueError: too many values to unpack

OpenCV找轮廓findContours报错 ValueError: not enough values to unpack (expected 3,got 2) 问题指向这行代码&#x1f447; binary, cnts, hierarchy cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) 报错的意思是需要3个返回值但只给了两…

【C++】快速排序的学习和介绍

前言 本篇文章我们先会学习快速排序这个算法&#xff0c;之后我们会学习sort这个函数 分治算法 在学习快速排序之前&#xff0c;我们先来学习一下分治算法&#xff0c;快速排序就是分治算法的一种&#xff0c;下面是分治算法的介绍&#xff0c; 分治算法&#xff0c;就是”…

设计模式-迭代器

文章目录 1. 引言1.1 概述1.2 设计模式1.3 迭代器模式的应用场景1.4 迭代器模式的作用 2. 基本概念2.1 迭代器 Iterator2.2 聚合 Aggregate2.3 具体聚合 ConcreteAggregate 3. Java 实现迭代器模式3.1 Java 集合框架3.2 Java 迭代器接口3.3 Java 迭代器模式实现示例 4. 迭代器模…

ESP32系列ESP32-D0WD双模BLE4.2+2.4G WIFI SoC芯片

目录 ESP32系列简介ESP32系列SoC功能框图ESP32-D0WD-V3芯片特性 ESP32系列SoC对比 ESP32系列简介 ESP32-DU1906和ESP32-DU1906-U两款AI模组&#xff0c;是基于ESP32-D0WD-V3芯片和语音芯片DU1906设计&#xff0c;集Wi-Fi、 传统蓝牙、低功耗蓝牙性能&#xff0c;以及音频语音处…

11.添加侧边栏,并导入数据

修改CommonAside的代码&#xff1a; <template><div><el-menu default-active"1-4-1" class"el-menu-vertical-demo" open"handleOpen" close"handleClose":collapse"isCollapse"><!--<el-menu-it…

管理类联考——逻辑——形式逻辑——汇总篇——知识点突破——假言——各种假言

角度 多重假言 &#xff08;1&#xff09;如果A&#xff0c;那么B&#xff0c;除非C。 符号化为&#xff1a;┐C→ (A→B)。 等价于&#xff1a;┐C→ (┐A∨B)。 等价于&#xff1a;C∨(┐A∨B)。 等价于&#xff1a;C∨┐A∨B。 等价于&#xff1a;┐(C∨┐A&#xff09;→…

K8S自动化运维容器化(Docker)集群程序

K8S自动化运维容器化集群程序 一、K8S概述1.什么是K8S2.为什么要用K8S3.作用及功能 二、K8S的特性1.弹性伸缩2.自我修复3.服务发现和复制均衡4.自动发布和回滚5.集中化配置管理和秘钥管理6.存储编排7.任务批量处理运行 三、K8S的集群架构1.架构2.模式3.工作4.流程图 四、K8S的核…

电子电路原理题目整理(2)

半导体是一种既不是导体也不是绝缘体的材料&#xff0c;其中包含自由电子和空穴&#xff0c;空穴的存在使半导体具有特殊的性质。 1.为什么铜是电的良导体&#xff1f; 从原子结构来看&#xff0c;铜原子的价带轨道上有一个价电子&#xff0c;由于核心和价电子之间的吸引力很弱…

【zookeeper】zookeeper的shell操作

Zookeeper的shell操作 本章节将分享一些zookeeper客服端的一些命令&#xff0c;实验操作有助于理解zookeeper的数据结构。 Zookeeper命令工具 在前一章的基础上&#xff0c;在启动Zookeeper服务之后&#xff0c;输入以下命令&#xff0c;连接到Zookeeper服务。连接成功之后&…