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

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

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

教程主要包含下面内容:

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

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

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

常见场景:

  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

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

  1. tomcat线程循环异常终止_腾讯面试官:如何停止一个正在运行的线程?我一脸蒙蔽。。。...

    1. 停止不了的线程 2. 判断线程是否停止状态 3. 能停止的线程--异常法 4. 在沉睡中停止 5. 能停止的线程---暴力停止 6.方法stop()与java.lang.ThreadDeath异 ...

  2. hashmap为什么线程不安全_面试官:你说 HashMap 线程不安全,它为啥不安全呢?...

    扫描下方海报 试读 本文来源: http://cnblogs.com/developer_chan/p/10450908.html 我们都知道HashMap是线程不安全的,在多线程环境中不建议使用,但 ...

  3. hashmap为什么线程不安全_为什么HashMap线程不安全?

    本篇内容:1500+字 建议阅读时间:4分钟 我们都知道 HashMap 是线程不安全的,那 HashMap 为什么线程不安全?JDK1.8 还有这些问题吗?如何解决这些问题呢?本文将对该问题进行解密 ...

  4. hashmap是有序的吗_这里有675道Java面试题,你准备好接招了吗?(完整版)

    上周发布了418道Java面试题之后, 有粉丝留言:希望可以尽快更新题集. 这次,675道面试题,全部送给你! 还有粉丝留言:不知道怎么获取答案? 分享本文至朋友圈,集赞3个及以上,截图发送公众号对话 ...

  5. java如何写线程外部类_廖雪峰Java读书笔记(六)--多线程(或称并发)

    1. 多线程基础 首先要明白一些概念: 进程:把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程. 线程:某些进程内部还需要同时执行多个子任务.例 ...

  6. hashmap存多少条数据_干货 | 面试官想问的HashMap,都在这一篇里面了!

    来源公众号:非科班的科班 本文思维导图 HashMap简介 HashMap 是很常用的一种集合框架,其底层实现方式在 JDK 1.7和 JDK 1.8中却有很大区别.HashMap 是用来存储数据的, ...

  7. 线程停止继续_晓龙吊打面试官系列: 如何优雅的停止一个线程

    一.什么时候我们需要中断一个线程 在实际的开发中,有很多场景需要我们中断一个正在运行的线程,就比如: 当我们使用抢票软件时,其中某一个通道已经抢到了火车票,这个时候我们就需要通知其他线程停止工作. 当 ...

  8. c++ socket线程池原理_一篇文章看懂 ThreadLocal 原理,内存泄露,缺点以及线程池复用的值传递问题...

    编辑:业余草来源:https://www.xttblog.com/?p=4946 一篇文章看懂 ThreadLocal 原理,内存泄露,缺点以及线程池复用的值传递问题. ThreadLocal 相信不 ...

  9. python 线程超时设置_爬虫基础知识(一)多线程与threading模块

    点击上方"蓝字"关注我们,第一时间推送优质文章! 前言 "本期带来的文章是python中多线程与threading模块的主要内容.主要分为「并发与并行」,「进程与线程」, ...

最新文章

  1. 【Android 插件化】插件化框架整理
  2. SQL Server表分区的NULL值问题
  3. CodeForces - 894B Ralph And His Magic Field(组合数学+思维)
  4. C/C++学习之路: STL
  5. oracle adg切换原理,oracle11g ADG主备切换
  6. 查了一晚上的资料,累啊。
  7. DDD领域模型、贫血模型、充血模型概念总结
  8. free store VS heap(自由存储区VS堆)
  9. 计算机网络 中国大学MOOC 哈尔滨工业大学 习题答案
  10. 下载谷歌浏览器官方正式(稳定)版以及历史各种版本
  11. 彻底理解connection timeout
  12. 用Python爬了我的微信好友,他们是这样的...
  13. 礼金记账本安卓_份子钱记账本-全民都爱用的随礼管理手账神器
  14. 使用cloud studio建立免费的云主机
  15. Mapbox之栅格矢量瓦片
  16. python做相册_《自拍教程73》Python 自动生成相册文件夹
  17. 程序设计思维与实践 CSP-M2 (3/4/数据班)
  18. 初步认识数据库:数据、数据库、数据库管理系统、数据库系统分别是什么?
  19. STM32 - Printf重定向使用微库、不使用微库(Keil) (转载)
  20. 好记性不如烂笔头之 App widgets(二)

热门文章

  1. python画一个祝福别人生日快乐_分享快乐给朋友的生日快乐祝福语生日贺卡句子...
  2. vba 定义类_VBA中类的介绍及应用简介
  3. 阿里妈妈品牌广告价值建模
  4. python程序员专用壁纸_代码没写完,哪里有脸睡觉!17 张程序员壁纸推荐
  5. 我的世界手机版服务器显示即将推出,我的世界手机版领域服即将开启 什么是领域服...
  6. linux 文件系统---类型、创建、
  7. CF--思维练习--CodeForces - 219C Color Stripe (思维)
  8. springcloud 之 路由网关 zuul
  9. 【Ubuntu-Tensorflow】GPU设置及显存资源分配
  10. python assert断言的用法