【多线程】线程安全、锁的同步和异步

2023-11-14

一、基本概念

       线程安全:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。

       非线程安全:非线程主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。

       synchronized:可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区

二、线程安全举例

       单纯地看线程安全的定义肯定会觉得非常晦涩。接下来通过下面的程序进行理解。

public class MyThread extends Thread{
	
	private int count = 5 ;
	
	//synchronized加锁
	public void run(){
		count--;
		System.out.println(this.currentThread().getName() + " count = "+ count);
	}
	
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread,"t1");
		Thread t2 = new Thread(myThread,"t2");
		Thread t3 = new Thread(myThread,"t3");
		Thread t4 = new Thread(myThread,"t4");
		Thread t5 = new Thread(myThread,"t5");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}

        实例变量count的初始值为5,进行i--操作,我们的预期结果是输出 count = 4 、count = 3、count = 2、count = 1、 count = 0。然而实际输出结果如下:

    

       实际数据结果合我们预期结果不相符。对比到定义就是这个类没有表现出正确的行为。所以说自定义类MyThread是线程不安全的。

       当我们在方法run()前加上synchronized关键字,输出结果如下:

      

       此时,我们的自定义类MyThread就是线程安全的。在这个例子中,我们实例化了一个对象myThread。开启了5个线程t1、t2、t3、t4、t5。(一个对象对应一把锁

        当多个线程访问myThread的run方法时,以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的),一个线程想要执行synchronized修饰的方法里的代码,首先是尝试获得锁,如果拿到锁,执行synchronized代码块中的内容:拿不到锁,这个线程就会不断尝试获得这把锁,知道拿到位置,而且是多个线程同时去竞争这把锁。也就是说会有锁竞争的问题。

       这个例子中,一旦t2执行完释放锁,其他4个线程就会去抢这把锁。加入开启了1000个线程,一个线程释放锁之后,剩下999个线程去抢一把锁。将会导致CPU飙升,甚至宕机。

三、局部变量、实例变量与线程安全

      方法内部的私有变量,也就是局部变量永远是线程安全的。如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题(上例就是)。run()方法关键字synchronized修饰之后就变成同步方法;在多个线程同时访问同一个对象的同步方法时一定是线程安全的。

四、多个对象多个锁

public class MultiThread {

	private int num = 0;
	
	/** static */
	public synchronized void printNum(String tag){
		try {
			
			if(tag.equals("a")){
				num = 100;
				System.out.println("tag a, set num over!");
				Thread.sleep(1000);
			} else {
				num = 200;
				System.out.println("tag b, set num over!");
			}
			
			System.out.println("tag " + tag + ", num = " + num);
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	//注意观察run方法输出顺序
	public static void main(String[] args) {
		
		//俩个不同的对象
		final MultiThread m1 = new MultiThread();
		final MultiThread m2 = new MultiThread();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				m1.printNum("a");
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override 
			public void run() {
				m2.printNum("b");
			}
		});		
		
		t1.start();
		t2.start();
	}

}
代码执行结果输出如下;

     被synchronized修饰的方法printNum()是同步方法。但输出结果给人的感觉却是异步的,我们最初预想的输出情况是  tab a,set num over!之后紧跟 tag a,num=100。这是为何呢?

      前面已经提及过一个对象一把锁。N个对象N个锁。上面的示例是两个线程分别访问同一个类的两个不同对象的同步方法。关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁。所以示例代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁。

      因为有2个对象,所以有2个锁,每个线程都可以拿到自己指定的锁,分别获得锁之后,执行synchronized同步方法体的内容。也就是时候两个线程t1、t2分别得到的是m1对象的锁和m2对象的锁,彼此之间是没有冲突的。


       有一种情况则是相同的锁,即在static方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)),我们接着对上边的代码进行修改,在 synchronized void printNum()前加上static修饰,在int num=0前也加上static关键字,“类锁”输出结果如下:

      

六、对象锁的同步和异步

       同步:synchronized

       同步的概念就是共享,我们要牢牢记住“共享”两个字,如果不是共享的资源,就没有必要进行同步。同步的目的就是为了线程安全,其实对于线程安全来说,需要满足两个特性:①原子性(同步)、②可见性。

       异步:asynchronized

       异步的概念就是独立的、相互之间不受到任何约束,就好像我们学习http的时候,在页面发起Ajax请求,我们还可以继续浏览或操作页面的内容,两者之间没有任何关系。

七、脏读:

       上面所有的例子,类都是只有一个同步方法。如果类中有1个同步方法,1个异步方法。

       1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized方法。

       2)A线程先只有object对象的Lock锁,B线程如果这在这时调用object对象中的synchronized方法则需等待。


public class DirtyRead {

	private String username = "dmsd";
	private String password = "123";
	
	public synchronized void setValue(String username, String password){
		this.username = username;
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		this.password = password;
		
		System.out.println("setValue最终结果:username = " + username + " , password = " + password);
	}
	
	/**
	 * synchronized
	 */
	public  void getValue(){
		System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
	}
	
	
	public static void main(String[] args) throws Exception{
		
		final DirtyRead dr = new DirtyRead();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				dr.setValue("z3", "456");		
			}
		});
		t1.start();
		Thread.sleep(1000);
		
		dr.getValue();
	}
	
}
上面的例子最终的输出结果为: 

      

         虽然在赋值时进行了同步,但在取值时出现了意外,也就是“脏读”。发生脏读的情况是在读取实例变量时,此值已经被其他线程更改了。解决上述脏读的办法就是在getValue()方法前加上synchronized关键字。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【多线程】线程安全、锁的同步和异步 的相关文章

  • C++多线程(七):unique_lock详解

    目录 unique lock取代lock guard unique lock的第二个参数 std adopt lock std try to lock std defer lock unique lock的成员函数 成员函数lock 成员函
  • 【JavaEE初阶】第九节.多线程 (基础篇)定时器(案例三)

    作者简介 大家好 我是未央 博客首页 未央 303 系列专栏 JavaEE初阶 每日一句 人的一生 可以有所作为的时机只有一次 那就是现在 前言 一 定时器概述 二 定时器的实现 2 1 Java标准库 定时器的使用 2 2 自己模拟实现一
  • 线程相关面试题

    1 ThreadPoolExecutor 线程池执行 有哪些常用的方法 1 执行线程池 submit excute 2 终止线程池 shutdown 3 判断线程是否终止 isShutdown 4 获取正在运行的线程数 getAcitive
  • JUC编程

    1 JUC JUC就是java util concurrent工具包的简称 这是一个处理线程的工具包 JDK 1 5开始出现的 1 传统的synchronized public class Synchronized public stati
  • Disruptor 详解

    Disruptor 详解 想了解一个项目 最好的办法就是 把它的源码搞到本地自己捣鼓 在网上看了 N 多人对 Disruptor 速度的吹捧 M 多人对它的机制分析 就连 Disruptor 官方文档中 也 NB 哄哄自诩 At LMAX
  • Qt启动页多线程

    Qt启动页多线程 项目需要在Qt进入启动页之前加载一个大模型文件 同时要有一个页面用来提示用户加载状态 这里就需要用到多线程了 如果在单线程操作 要么需要等到文件加载完毕后才能显示等待页 要么干脆跳过了模型文件的加载 都是不符合需求的 我们
  • 如何在spring框架中解决多数据源的问题

    在我们的项目中遇到这样一个问题 我们的项目需要连接多个数据库 而且不同的客户在每次访问中根据需要会去访问不同的数据库 我们以往在 spring 和 hibernate 框架中总是配置一个数据源 因而 sessionFactory 的 dat
  • .Net/C#: 实现支持断点续传多线程下载的 Http Web 客户端工具类 (C# DIY HttpWebClient)

    选择自 playyuer 的 Blog Net C 实现支持断点续传多线程下载的 Http Web 客户端工具类 C DIY HttpWebClient Reflector 了一下 System Net WebClient 重载或增加了若干
  • Java 线程关闭

    Java线程关闭的方式 1 使用状态位 public class CloseThread extends Thread boolean flag true int index 0 Override public void run while
  • 条件变量(condition variable)详解

    原理 假设我们需要解决这样一个问题 一个列表记录需要处理的任务 一个线程往此列表添加任务 一个线程processTask处理此列表中的任务 这个问题的一个关键点在于processTask怎么判断任务列表不为空 一般有两种方法 一 proce
  • 多线程快速处理List集合(结合线程池的使用)

    有一个大List集合 遍历进行一些耗时操作 不能达到性能要求 查询日志 单个任务虽然有不少数据库和第三方API请求 比较耗时 但返回效率尚可 所以优先采用多线程方式进行处理并行请求数据库和第三方API 因为处理完还要对list所属的数据进行
  • 【Java】网络编程——多线程下载文件

    前言 多线程下载文件 比单线程要快 当然 线程不是越多越好 这和获取的源文件还有和网速有关 原理 在请求服务器的某个文件时 我们能得到这个文件的大小长度信息 我们就可以下载此长度的某一个片段 来达到多线程下载的目的 每条线程分别下载他们自己
  • MFC多线程编程之一——问题提出

    原文地址 http www vckbase com document viewdoc id 1704 一 问题的提出 编写一个耗时的单线程程序 新建一个基于对话框的应用程序SingleThread 在主对话框IDD SINGLETHREAD
  • Java多线程(四):什么是死锁以及如何解决死锁

    目录 1 什么是死锁 2 死锁产生的原因 3 如何解决死锁问题 3 1 改变环路等待条件 3 2 破坏请求并持有条件 1 什么是死锁 死锁 是指两个或两个以上的进程在执行过程中 由于竞争资源或者由于彼此通信而造成的一种阻塞的现象 若无外力作
  • QT实现多线程,以及子线程调用主线程方法与变量

    实现思路 第一步需要将子线程声明为主线程的友元类 第二步是将主线程类对象的地址通过信号槽传递给子线程中创建的对象 使得子线程能访问主线程的数据的 1 子线程 displayresult h 头文件 伪代码 include tabwindow
  • wxwidgets编写多线程程序--wxThread

    细节描述 线程基本上来说是应用程序中一条单独执行的路径 线程有时被称为轻量级进程 但线程与进程的根本不同之处在于不同进程存储空间是相互独立的 而同一进程里的所有线程共享同一地址空间 尽管这使得它更容易共享几个线程间的普通数据 但这也使得它有
  • 多线程事务怎么回滚?说用 @Transactional 可以回去等通知了!

    背景介绍 1 最近有一个大数据量插入的操作入库的业务场景 需要先做一些其他修改操作 然后在执行插入操作 由于插入数据可能会很多 用到多线程去拆分数据并行处理来提高响应时间 如果有一个线程执行失败 则全部回滚 2 在spring中可以使用 T
  • 多线程案例:银行取钱

    不安全取钱 两个人去银行取钱 账户 银行取钱 给账户上锁 public class UnsafeBank public static void main String args 账户 Account3 account new Account
  • TaskDecatator用法

    在Spring框架中 TaskDecorator 是一个接口 它可以用来自定义由 ThreadPoolTaskExecutor 或其他任务执行器管理的任务的装饰行为 这通常用于在执行任务之前和之后添加某些上下文相关的行为 比如设置线程上下文
  • 线程安全的集合类

    Java中提供了许多集合类 其中有的是线程安全的 有的是线程不安全的 线程安全的集合类有 1 Vector Vector类实现了一个 动态数组 与ArrayList相似 但Vector是同步访问的 2 Stack Stack是Vector的

随机推荐

  • C++day01

    一 C 简介 本贾尼 斯特劳斯特卢普 于1979年4月在贝尔实验室负责分析UNIX系统的内核的流量情况 希望有一款更加模块化的工具 于1979年10月开始着手开发一种新的编程语言 在C语言的基础上增加了面向对象机制 这就是C 的来历 在19
  • 从量子物理到AI医疗,这位清华博士后想用十年弥补病理医生的"百年缺口"

    大数据文摘出品 作者 易琬玉 根据WHO下属 国际癌症研究机构 公布的最新全球癌症数据报告 2018年全球新增癌症诊断病例约1910万 死亡病例约960万 约1 5男性和1 6女性在一生中会罹患癌症 1 8男性和1 11女性因癌症而死亡 对
  • 安全应急响应案例

    1 产生背景 1988年11月发生的莫里斯蠕虫病毒事件 Morris Worm Incident 致使当时的互联网络超过10 的系统不能工作 该案件轰动了全世界 并且在计算机科学界引起了强烈的反响 为此 1989年 美国国防部高级研究计划署
  • linux系统ipcclean命令,Linux学习笔记29——IPC状态命令

    一 IPC IPC是进程间通讯 在前面 我们相继学习了进程间通讯机制有信号量 内存共享 消息队列 状态命令 ipcs 和删除命令 ipcrm 提供了一种检查和清理IPC机制的方法 二 状态命令 1 显示信号量状态用ipcs s 2 显示共享
  • 华为OD机试真题- 狼羊过河【2023Q2】【JAVA、Python、C++】

    题目描述 一农夫带着m只羊 n只狼过河 农夫有一条可载x只狼 羊的船 农夫在时或者羊的数量大于狼时 狼不会攻击羊 农夫在不损失羊的情况下 运输几次可以完成运输 返程不计入次数 输入描述 输入参数为 m n x m 为羊的数量 n为狼的数量
  • nodeJS入门(四)之身份验证

    身份验证 一 bcrypt模块 1 1 简单介绍一下 1 2 安装 1 3 加密 二 身份验证 2 1 session 会话 2 1 1 session实现身份验证的思路 2 1 2 session的业务流程 2 1 3 express s
  • QT学习总结

    转眼间工作已经三个月了 实习期也已经满了 在这三个月中 对qt的学习遇到了很多问题 也解决了很多问题 也留下了一些现在的未解之谜 在工作中关于qt的一些东西也需要进行一些记录 QT Creator对大于带有两个空格的目录和中文命名的目录不支
  • cv2.error: OpenCV(4.6.0) /io/opencv/modules/imgcodecs/src/grfmt_exr.cpp:103: error

    问题描述 cv2 error OpenCV 4 6 0 io opencv modules imgcodecs src grfmt exr cpp 103 error 213 The function feature is not impl
  • 数据库服务器配置参数修改,数据库服务器参数配置

    数据库服务器参数配置 内容精选 换一换 云搜索服务 Cloud Search Service 为用户提供结构化 非结构化文本的多条件检索 统计 报表 本章节介绍如何通过CDM将本地Elasticsearch整库迁移到云搜索服务中 流程如下
  • 如何在GitHub的repository中建立文件夹

    GitHub的repository无法直接建立文件夹 需要曲线建立 第一步 创建新的repository 若已有repository可直接将其打开 第二步 在repository界面右上角选择创建新文件 create new files 不
  • 费曼技巧学习笔记

    博主狂言 技巧正文 技巧的详细步骤 技巧步骤一 技巧步骤二 技巧步骤三 技巧的提纲总结 费曼技巧可以解决的问题 费曼技巧的另一种描述四步学习法 步骤一 步骤二 步骤三 步骤四 博主读后感 博主狂言 初识费曼技巧 甚是熟悉 博主自认有那么一点
  • 基于Springboot实现Kafka消费数据

    本文介绍使用Kafka监听和订阅两种不同方式进行数据消费 1 配置文件 spring kafka bootstrap servers 192 168 1 16 9092 消费者 consumer group id alarmService
  • MySQL check table/optimize table/analyze table/REPAIR TABLE

    check table 检查InnoDB和MyIsam是否有错误 检查表或者视图是否存在错误 对 MyISAM 和 InnoDB 存储引擎的表有作用 对于 MyISAM 存储引擎的表进行表检查 也会同时更新关键字统计数据 CHECK TAB
  • 《ios零基础教程》-- ”Mac OS X的介绍”和一些“基础语法” 2014-3-18总结

    一 学前须知 讲的ios和c语言的一些发展历史 作为了解即可 二 Mac OS X简单介绍 讲的是对苹果电脑的一些操作和基础了解 感觉蛮有用的 以前都是用的windows 突然用这种高富帅装备 还真不习惯 装个软件都费劲 看了这个之后 对m
  • 1 Centos7安装(jdk8)Tomcat9并设置为开机启动

    一 安装过程 cd usr local src wget http mirrors hust edu cn apache tomcat tomcat 9 v9 0 12 bin apache tomcat 9 0 12 tar gz 可以下
  • 面试余额宝Java技术岗(1-4面题目):GC+spring+二叉树+Dubbo+线上调优+​​​​数据库索引+mq

    余额宝一面 JVM 内存分哪几个区 每个区的作用是什么 JVM有哪些回收算法 对应的收集器有哪些 GC 的两种判定方法 CMS 收集器与 G1 收集器的特点 hashmap源码问题 HashMap HashMap如何保证线程安全 Concu
  • Aix6.1下安装SWFTools

    该文章参考http tech sxinfo net jctj article do curMenu 14 childMenu 16 articleId 388 再结合我在实际上在Aix6 1上的安装经验来编写 相关文件的下载 ftp ftp
  • Linux 同步工具rsync 文件目录同步,增量备份的手段之一

    一 rsync介绍 rsync是linux系统下的数据镜像备份工具 使用快速增量备份工具Remote Sync可以远程同步 支持本地复制 或者与其他SSH rsync主机同步 它的特性如下 可以镜像保存整个目录树和文件系统 可以很容易做到保
  • 工具类库系列(十四)-FileData

    第十四个工具类 FileData 用于文本文件读取 保存 以及在内存中修改文本文件内容 读取 ifstream 保存 ofstream 修改内容 以string类型 获取 设置 文本文件内容 最大支持文件大小1M 上代码 FileData
  • 【多线程】线程安全、锁的同步和异步

    一 基本概念 线程安全 当多个线程访问某一个类 对象或方法 时 这个类始终都能表现出正确的行为 那么这个类 对象或方法 就是线程安全的 非线程安全 非线程主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改 值不同步的情况