hashmap为什么线程不安全_什么时候线程不安全?怎样做到线程安全?怎么扩展线程安全的类?...

e6858225a5db3ab5bdaeb992dac36eef.png
本文同名博客老炮说Java:https://www.laopaojava.com/,每天更新Spring/SpringMvc/SpringBoot/实战项目等文章资料

顺便再给大家推荐一套SpringCloud微服务教程,方便学习:

SpringCloud微服务电商项目教程 - 老炮说Java-程序员编程资料和编程经验分享平台​www.laopaojava.com
76fa5259aa99071444d9885fe64c7e91.png

教程主要包含下面内容:

948699406fc6550ce4c24431f7ad21a1.png

当多个线程去访问某个类时,如果类会表现出我们预期出现的行为,那么可以称这个类是线程安全的。

什么时候会出现线程不安全?

操作并非原子。多个线程执行某段代码,如果这段代码产生的结果受不同线程之间的执行时序影响,而产生非预期的结果,即发生了竞态条件,就会出现线程不安全;

常见场景:

  1. count++。它本身包含三个操作,读取、修改、写入,多线程时,由于线程执行的时序不同,有可能导致两个线程执行后 count 只加了 1,而原有的目标确实希望每次执行都加 1;
  2. 单例。多个线程可能同时执行到instance == null成立,然后新建了两个对象,而原有目标是希望这个对象永远只有一个;
public MyObj getInstance(){if (instance == null){instance = new MyObj();}return instance}

解决方式是:当前线程在操作这段代码时,其它线程不能对进行操作

常见方案:

  1. 单个状态使用 java.util.concurrent.atomic 包中的一些原子变量类,注意如果是多个状态就算每个操作是原子的,复合使用的时候并不是原子的;
  2. 加锁。比如使用 synchronized 包围对应代码块,保证多线程之间是互斥的,注意应尽可能的只包含在需要作为原子处理的代码块上;

synchronized 的可重入性

当线程要去获取它自己已经持有的锁是会成功的,这样的锁是可重入的,synchronized 是可重入的
class Paxi {
public synchronized void sayHello(){
System.out.println("hello");
}
}
class MyClass extends Paxi{
public synchronized void dosomething(){
System.out.println("do thing ..");
super.sayHello();
System.out.println("over");
}
}
它的输出为
do thing ..
hello
over
复制代码
  • 修改不可见。读线程无法感知到其它线程写入的值

常见场景:

  1. 重排序。在没有同步的情况下,编译器、处理器以及运行时等都有可能对操作的执行顺序进行调整,即写的代码顺序和真正的执行顺序不一样, 导致读到的是一个失效的值
  2. 读取 long、double 等类型的变量。JVM 允许将一个 64 位的操作分解成两个 32 位的操作,读写在不同的线程中时,可能读到错误的高低位组合

常见方案:

  1. 加锁。所有线程都能看到共享变量的最新值;
  2. 使用 Volatile 关键字声明变量。只要对这个变量产生了写操作,那么所有的读操作都会看到这个修改;
注意:Volatile 并不能保证操作的原子性,比如count++操作同样有风险,它仅保证读取时返回最新的值。使用的好处在于访问 Volatile 变量并不会执行加锁操作,也就不会阻塞线程。

不同步的情况下如何做到线程安全?

  1. 线程封闭。即仅在单线程内访问数据,线程封闭技术有以下几种:
  • Ad-hoc 线程封闭。即靠自己写程序来实现,比如保证程序只在单线程上对 volatile 进行 读取-修改-写入
  • 栈封闭。所有的操作都反生执行线程的栈中,比如在方法中的一个局部变量
  • ThreadLocal 类。内部维护了每个线程和变量的一个独立副本
  1. 只读共享。即使用不可变的对象。
  • 使用 final 去修饰字段,这样这个字段的 “值” 是不可改变的
注意 final 如果修饰的是一个对象引用,比如 set, 它本身包含的值是可变的
  • 创建一个不可变的类,来包含多个可变的数据。
class OneValue{//创建不可变对象,创建之后无法修改,事实上这里也没有提供修改的方法private final BigInteger  last;private final BigInteger[] lastfactor;public OneValue(BigInteger  i,BigInteger[] lastfactor){this.last=i;this.lastfactor=Arrays.copy(lastfactor,lastfactor.length);}public BigInteger[] getF(BigInteger  i){if(last==null || !last.equals(i)){return null;}else{return Arrays.copy(lastfactor,lastfactor.length)}}}class MyService {//volatile使得cache一经更改,就能被所有线程感知到private volatile OneValue cache=new OneValue(null,null); public void handle(BigInteger i){BigInteger[] lastfactor=cache.getF(i);if(lastfactor==null){lastfactor=factor(i);//每次都封装最新的值cache=new OneValue(i,lastfactor)}nextHandle(lastfactor)}}

如何构造线程安全的类?

  1. 实例封闭。将一个对象封装到另一个对象中,这样能够访问被封装对象的所有代码路径都是已知的,通过合适的加锁策略可以确保被封装对象的访问是线程安全的。
java 中的 Collections.synchronizedList 使用的原理就是这样。部分代码为
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
复制代码

SynchronizedList 的实现, 注意此处用到的 mutex 是内置锁

static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
}
复制代码

mutex 的实现

static class SynchronizedCollection<E> implements Collection<E>, >Serializable {private static final long serialVersionUID = 3053995032091335093L;final Collection<E> c;  // Backing Collectionfinal Object mutex;     // Object on which to synchronizeSynchronizedCollection(Collection<E> c) {if (c==null)throw new NullPointerException();this.c = c;mutex = this; // mutex实际上就是对象本身}

什么是监视器模式

java 的监视器模式,将对象所有可变状态都封装起来,并由对象自己的内置锁来保护, 即是一种实例封闭。比如 HashTable 就是运用的监视器模式。它的 get 操作就是用的 synchronized,内置锁,来实现的线程安全

public synchronized V get(Object key) {Entry tab[] = table;int hash = hash(key);int index = (hash & 0x7FFFFFFF) % tab.length;for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {if ((e.hash == hash) && e.key.equals(key)) {return e.value;}}return null;
}
  • 内置锁

每个对象都有内置锁。内置锁也称为监视器锁。或者可以简称为监视器
线程执行一个对象的用 synchronized 修饰的方法时,会自动的获取这个对象的内置锁,方法返回时自动释放内置锁,执行过程中就算抛出异常也会自动释放。
以下两种写法等效:

synchronized void myMethdo(){//do something
}
void myMethdo(){ synchronized(this){//do somthding} }
官方文档
  • 私有锁
public class PrivateLock{private Object mylock = new Object(); //私有锁void myMethod(){synchronized(mylock){//do something}}
}

它也可以用来保护对象,相对内置锁,优势在于私有锁可以有多个,同时可以让客户端代码显示的获取私有锁

  • 类锁

在 staic 方法上修饰的,一个类的所有对象共用一把锁

  1. 把线程安全性委托给线程安全的类

如果一个类中的各个组件都是线程安全的,该类是否要处理线程安全问题?

视情况而定。

  1. 只有单个组件,且它是线程安全的。
public class DVT{private final ConcurrentMap<String,Point> locations;private final Map<String,Point> unmodifiableMap;public DVT(Map<String,Point> points){locations=new ConcurrentHashMap<String,Point>(points);unmodifiableMap=Collections.unmodifiableMap(locations);}public Map<String,Point> getLocations(){return unmodifiableMap;}public Point getLocation(String id){return locations.get(id);}public void setLocation(String id,int x,int y){if(locations.replace(id,new Point(x,y))==null){throw new IllegalArgumentException("invalid "+id);}}}public class Point{public final int x,y;public Point(int x,int y){this.x=x;this.y=y;}}

线程安全性分析

  • Point 类本身是无法更改的,所以它是线程安全的,DVT 返回的 Point 方法也是线程安全的
  • DVT 的方法 getLocations 返回的对象是不可修改的,是线程安全的
  • setLocation 实际操作的是 ConcurrentHashMap 它也是线程安全的

综上,DVT 的安全交给了‘locations’,它本身是线程安全的,DVT 本身虽没有任何显示的同步,也是线程安全。这种情况下,就是 DVT 的线程安全实际是委托给了‘locations’, 整个 DVT 表现出了线程安全。

  1. 线程安全性委托给了多个状态变量
    只要多个状态变量之间彼此独立,组合的类并不会在其包含的多个状态变量上增加不变性。依赖的增加则无法保证线程安全
public class NumberRange{
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);public void setLower(int i){//先检查后执行,存在隐患if (i>upper.get(i)){throw new IllegalArgumentException('can not ..');}lower.set(i);}public void setUpper(int i){//先检查后执行,存在隐患if(i<lower.get(i)){throw new IllegalArgumentException('can not ..');}upper.set(i);}}

setLower 和 setUpper 都是‘先检查后执行’的操作,但是没有足够的加锁机制保证操作的原子性。假设原始范围是 (0,10), 一个线程调用 setLower(5), 一个设置 setUpper(4) 错误的执行时序将可能导致结果为(5,4)

如何对现有的线程安全类进行扩展?

假设需要扩展的功能为 ‘没有就添加’。
  1. 直接修改原有的代码。但通常没有办法修改源代码
  2. 继承。继承原有的代码,添加新的功能。但是同步策略保存在两份文件中,如果底层同步策略变更,很容易出问题
  3. 组合。将类放入一个辅助类中,通过辅助类的操作代码。比如扩展 Collections.synchronizedList。期间需要注意锁的机制,错误方式为
public class ListHelper<E>{public List<E> list=Collections.synchronizedList(new ArrayList<E>());...public synchronized boolean putIfAbsent(E x){boolean absent = !list.contains(x);if(absent){list.add(x);}return absent;}}

这里的 putIfAbsent 并不能带来线程安全,原因是 list 的内置锁并不是 ListHelper, 也就是 putIfAbsent 相对 list 的其它方法并不是原子的。Collections.synchronizedList 是锁在 list 本身的,正确方式为

public  boolean putIfAbsent(E x){synchronized(list){boolean absent = !list.contains(x);if(absent){list.add(x);}return absent;}
}

另外可以不管要操作的类是否是线程安全,对类统一添加一层额外的锁。实现参考 Collections.synchronizedList 方法

作者:爬蜥https://juejin.im/post/5b7d68f66fb9a019d80a9002

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

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

相关文章

光遇服务器维护都在干什么,光遇:全图毕业后该做什么?老玩家建议,这四件事不做会后悔...

原标题&#xff1a;光遇&#xff1a;全图毕业后该做什么&#xff1f;老玩家建议&#xff0c;这四件事不做会后悔光遇&#xff1a;全图毕业后该做什么&#xff1f;老玩家建议&#xff0c;这四件事不做会后悔前言&#xff1a;大家好&#xff0c;我是喜欢玩游戏、聊游戏的孟婆小叔…

加仓减仓口诀_做短线必备口诀

October短线口诀早上大跌可加仓&#xff0c;早上大涨要减仓下午大涨要减仓&#xff0c;下午大跌次日买上午下跌不买股&#xff0c;逢低加仓T0下午拉高不追涨&#xff0c;逢高减仓T1量价买卖口诀高位无量就拿&#xff0c;就算拿错也要拿高位放量趁早跑&#xff0c;哪怕跑错也要跑…

包无法安装_R语言基础教程——第2章:R下载安装和环境搭建

R语言安装包下载 官网下载R安装包 下载地址为&#xff1a;https://cran.r-project.org 进入链接&#xff0c;如下图所示&#xff0c;在页面顶部提供了三个下载链接&#xff0c;分别对应三种操作系统&#xff1a;Windows、Mac和Linux。请选择自己操作系统对应的链接。这里以Wind…

charat越界返回什么_Java基本语法方法修饰符返回类型参数列表异常列表

方法[method name]定义方法[method]是类的成员之一&#xff0c;(因为在java中&#xff0c;方法不能单独定义&#xff0c;它必须在类之中)。修饰符 返回类型 方法名()[throws 异常类型]{ //方法体}方法名字&#xff1a;只要是一个合法的名字就可以&#xff0c;尽可能有意义。如&…

打包指令_Linux系统常用指令总结

来源 | CSDN 博客作者 | 不撸代码闲得慌&#xff0c;责编 | Carol出品 | CSDN云计算(ID&#xff1a;CSDNcloud)系统的运行级别0&#xff1a;关机1&#xff1a;单用户模式(可以找回丢失的密码)2&#xff1a;多用户状态没有网络服务3&#xff1a;多用户状态有网络服务4&#xff1…

cmd管道无法接收特定程序返回值_渗透不会反弹shell?来教你写一个cmd的shell

渗透不会反弹shell&#xff1f;来教你写一个cmd的shell包含的库&#xff1a;#include #include #include #include #include #pragma comment(lib, "Ws2_32.lib")#define DEFAULT_BUFLEN 1024winsock2和ws2tcpip两个库文件是用来初始化网络套接字的。windows用来初始…

有类似split的命令吗_5分钟学linux命令之split

情景介绍平时工作中&#xff0c;我习惯使用rz从本地上传文件到服务器&#xff0c;sz从服务器下载文件到本地&#xff0c;但对传输文件大小有限制&#xff0c;例如排查线上jvm的问题&#xff0c;需要生成了dump文件&#xff0c;可能有10G大&#xff0c;超过了限制&#xff0c;怎…

乐观锁的颗粒度_MySql数据库锁机制详解

概述数据库锁定机制简单的来说&#xff0c;就是数据库为了保证数据的一致性与完整性&#xff0c;而使各种共享资源在被并发访问时变得有序所设计的一种规则。对于任何一种数据库来说都需要有相应的锁机制&#xff0c;所以MySQL也不能例外。MySQL数据库根据锁锁定数据的颗粒度可…

云溪怎么导入dxf_dwg怎么转换成dxf文件?超详细图文教程分享

在CAD相关的工作中,我们经常遇到这种问题&#xff0c;需要把DWG格式的CAD图纸转换成DXF格式。那么&#xff0c;我们应该怎么做呢&#xff1f;接下来为你介绍这一个方法&#xff0c;非常方便快速哦。准备电脑风云CAD转换器具体步骤&#xff1a;在桌面上新建文件夹&#xff0c;将…

特斯拉是l3还是l2_比特斯拉还“高一级”,长安的“L3级自动驾驶”到底什么来头?...

3月5日&#xff0c;长安发布了旗下全新轿跑SUV——UNI-T&#xff0c;新车一经亮相&#xff0c;便凭借极具科幻的造型&#xff0c;以及越级的动力性能吸粉无数。而在大家意犹未尽之时&#xff0c;长安又在3月10日&#xff0c;通过UNI-T发布了“L3级自动驾驶”量产体验&#xff0…

mysql5.7.17配置_mysql-5.7.17-winx64的安装配置

第一步&#xff1a;下载mysql-5.7.17-winx64解压版本&#xff1a;http://dev.mysql.com/downloads/mysql/第二步&#xff1a;解压到安装目录&#xff0c;如&#xff1a;C:\myprogram\mysql-5.7.17-winx64第三步&#xff1a;设置环境变量操作如下&#xff1a;1)右键单击我的电脑…

openstack实例控制台显示响应时间过长_监控OpenStack的技巧

如果你以前曾在云平台上工作过&#xff0c;你一定熟悉这些系统的分布式和解耦性质。解耦的分布式系统依赖于微服务来执行特定的任务&#xff0c;每个微服务都会暴露自己的REST(表示状态转移)API。这些微服务通常以诸如RabbitMQ或QPID等消息中间件的形式通过轻量级消息层相互通信…

java8 创建list方式_Java 8 创建 Stream 的 10 种方式,我保证你受益无穷!

今天来分享下在 Java 8 中创建 Stream 的 10 种方式,我就整理了 10 种,其实还有更多,仅供大家参考学习下。 1、Stream.of 可变参数 Stream<String> stream1 = Stream.of("A", "B", "C"); System.out.println("stream1:" + st…

js map满足条件跳出循环_js.es5 map循环一大坑:循环遍历竟然出现逗号!

一、mapmap大法好作为当今程序界最好用的循环方法之一map, 在我的项目里基本替代了for循环map循环常用的一些方法/********* ES6 **********///一行代码可以省略returnarray.map( item > console.log(item))>array.map( item > {return console.log(item)})//多行代码…

micropython按键控制流水灯_【micro:bit Micropython】The LED Display(1)控制像素点

使用DFrobot研发的micropython编程软件uPyCraft&#xff0c;下载固件(Firmware)和下载程序都非常方便。可以在DFrobot论坛中进行下载。uPyCraft软件运行界面官网中的micro:bit Micropython API介绍得非常详细&#xff0c;为开发人员提供了详细的文字说明和参照。micro:bit Micr…

python中exp_python中的exp是什么

Python exp() 函数描述exp() 函数返回 x 的指数&#xff0c;。语法import mathmath.exp(x)注意&#xff1a;exp() 是不能直接访问的&#xff0c;需导入 math 模块&#xff0c;通过静态对象调用该方法。参数x -- 数值表达式。返回值返回 x 的指数&#xff0c;。实例# -*- coding…

python干货_python 基础干货 02

list 与 tuplelist 类似 数组tuple 跟 list 一样, 只是一旦定义, 里边的内容不可以改变.这样, 上边的内容就不可以改变了."可变的" tuple, 不是说 tuple 是不可以改变的么?想内存dict 与 setdict 是 python内置字典, 其他语言中称为 map, 使用键-值(key-value)存储&…

mysql安装目录问题_Windows下MySQL的安装目录问题

今天发现一个MySQL的问题&#xff0c;当把MySQL的文件目录安放在t字母打头的目录下时&#xff0c;比如d:/test目录&#xff0c;mysql服务就起不来了&#xff0c;报告找不到文件:D:/test/mysql>bin/mysqld-nt.exe --console090811 10:09:55 [ERROR] Cant find messagefile D:…

大学考试分数越高学分越多吗_大学的绩点和学分有什么用?影响学生毕业吗

原标题&#xff1a;大学的绩点和学分有什么用&#xff1f;影响学生毕业吗网友一&#xff1a;读大学也要关心成绩&#xff0c;绩点和学分是两个重要指标&#xff0c;到底是什么&#xff0c;怎么计算&#xff1f;有什么用处&#xff1f;为什么很重要&#xff1f;面试官最看重哪些…

pandas输出到excel_python读写excel等数据文件方法汇总

python处理数据文件第一步是要读取数据&#xff0c;文件类型主要包括文本文件&#xff08;csv、txt等&#xff09;、excel文件、数据库文件、api等。下面整理下python有哪些方式可以读取数据文件。1. python内置方法&#xff08;read、readline、readlines&#xff09;read()&a…