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;哪怕跑错也要跑…

神雕侠侣2服务器维护,《神雕侠侣2》手游10月22日停服维护公告

亲爱的少侠为了给您带来更好的游戏体验,《神雕侠侣2》手游将于10月22日09:00-10:00进行例行停服维护&#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…

python秒转化为时间格式_Python耗费时间秒转 天小时分钟秒 时间格式美化

在工作中经常会遇到将耗时 转换为天小时分秒的情况本Demo 中divmod默认返回元组&#xff0c;同时利用递归的思想# -*- coding: utf-8 -*-def seconds_format(time_cost: int):"""耗费时间格式转换:param time_cost::return:"""min 60hour 60 *…

angular做语言切换_angular多语言配置详解

angular的国际化方案&#xff0c;采用ngx-translate来实现。安装模块&#xff1a;npm install ngx-translate/core --save在根模块中导入&#xff1a;// other moduleimport {TranslateModule} from ngx-translate/core;NgModule({declarations: [AppComponent,],imports: [// …

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…

unreal4怎么设置游戏模式_在Unreal4中如何连接自定义服务器

如何在Unreal4中连接自定义服务器&#xff1f;UE4 dedicated server是一个很好的游戏服务器&#xff0c;但是对于大厅和聊天服务器来说&#xff0c;我们要自行开发&#xff0c;通过UE4的socket组建很方便的和其他服务器建立连接。创建SocketFSocket* Socket ISocketSubsystem:…

mysql8 优化_MySQL 8.0 优化

优化效果(从大到小)&#xff1a;硬件设备 -> MySQL数据库 -> Linux操作系统 -> 表设计—————————————————————————————————————-1硬件设备优化提升硬件设备&#xff0c;例如使用SSD高速磁盘&#xff0c;CPU等。—————————…

mysql的timestamp类型_MySQL数据库中的timestamp类型与时区

MySQL的timestamp类型时间范围between 1970-01-01 00:00:01 and 2038-01-19 03:14:07&#xff0c;超出这个范围则值记录为0000-00-00 00:00:00&#xff0c;该类型的一个重要特点就是保存的时间与时区密切相关&#xff0c;上述所说的时间范围是UTC(Universal Time Coordinated)标…

mysql处理时间_MYSQL时间处理  (转)

1.存储日期时间用now();2.存储日期用curdate()/current_date;3.存储时间用time();4.获取年份用year();5.获取月份用month();6.获取月份中的日期用day()或者dayofmonth();7.获取昨天就用date_sub(curdate(),interval 1day)/date_sub(current_date,interval 1 day);或者curdate()…

mysql导入数据比原来多_Oracle和MySQL的数据导入,差别为什么这么大

经常会有一些朋友咨询我一些数据库的问题&#xff0c;我注意到一个很有意思的现象&#xff0c;凡是数据导入的问题&#xff0c;基本上都是Oracle类的&#xff0c;MySQL类的问题脑子里想了下竟然一次都没有。我禁不住开始思考这个未曾注意的问题&#xff1a;为什么Oracle导入数据…

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)右键单击我的电脑…

mysql 8核16g参数优化_问个 MySql 优化问题, 16G, 8 核服务器??

服务器配置&#xff1a;处理器&#xff1a;Xeon E3-1230 V2内存&#xff1a;16G DDR3 1600Mhz硬盘&#xff1a;1TB 企业级硬盘我现在的配置[mysqld]port xxxxsocket xxxdatadir xxxxdefault_storage_engine MyISAM#skip-external-locking#loose-skip-innodbkey_buffer_size …