什么是线程不安全

我对线程安全的理解就是多个线程同时操作一个共享变量时会产生意料之外的情况,这种情况就是线程不安全。注意:只有写操作才可能出现线程不安全,对共享变量只进行读操作线程是绝对安全的。

具体线程不安全的例子有一个很经典的就是两个线程都对一个共享变量x=0执行100次自增操作,但是x的结果并非200

因此线程不安全的条件是:多线程 + 共享变量 + 写操作

Java的内存模型

你可能会好奇线程是如何获取共享变量的,这就为你解答

Java线程之间的通信由Java内存模型(简称JMM)控制,从抽象的角度来说,JMM定义了线程和主内存之间的抽象关系。JMM的抽象示意图如图所示:

从图中我们可以看到:

  • 共享变量存在于主内存中,也就是堆内存
  • 每一个线程都保存了一份该线程使用到的共享变量的副本
  • 线程读取共享变量优先从本地内存(也就是栈内存)中读取,写共享变量先写到栈内存,再写入堆内存
  • 线程之间对共享变量的通信只能通过堆内存

以上只是Java内存模型的抽象图,实际上线程的工作模型是这样的,栈内存即是两个缓冲区

接下来看一个线程不安全的例子:

假设线程A、B操作同一个共享变量X,初始两级Cache都为空

  1. 线程A想要读取X的值,由于两级Cache都没有命中,因此加载堆内存中的X=0,并缓存到两个Cache中
  2. 线程A修改X的值为1,为为两个Cache刷新X,再刷新到堆内存
  3. 线程B想要获取X的值,一级缓存没有获取到,二级缓存命中,读取到X=1
  4. 线程B想要修改X的值为2,先刷新自,己的一级缓存为2,再刷新二级缓存和堆内存中的X为2。目前为止一切正常,接下来重点来了
  5. 线程A想要读取X的值,一级缓存命中此时X=1,但是堆内存中的X=2。可以看到线程B写入的共享变量对X不可见,出现了线程不安全的情况。

由于Java内存机制就是这样设计的,因此多个线程操作同一个变量会产生不安全的问题,volatitle关键字这是被设计出来解决这一问题的,它只能用于单个变量

volatile解决共享变量线程不安全的策略

还是接着上面这个例子,我们这样定义X

volatle int X=0

volatile的内存语义是:

当一个线程对volatile 修饰的变量进行写操作时,JMM会立即将该线程对应的栈内存中的副本的值刷新到堆内存中;当一个线程对volatile 修饰的变量进行读时,JMM会清空此变量的一二级缓存,直接从堆内存中读取共享变量的值。

volatile可以当作一个轻量级的锁来使用,但volatile仅仅只能保证共享变量内存的可见性,不能保证操作共享变量的原子性,而锁(如synchronized)能保证整段锁范围内的代码具有原子性。

synchronized与锁

首先要明确的是synchronized不是锁,锁都是基于对象的(Object的子类),Java中的每一个对象都可以作为一个锁。

synchronized是Java的一个关键字,保证临界区内的代码同一时刻只能有一个线程执行。

线程的执行代码在进入synchronized代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的wait系列方法时释放该内置锁。内置锁是排它锁,也就是当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁。

sysnchronized的内部锁可以是:

  • 当前类的class字节码对象:this.getClass
  • 当前类的一个实例:this
  • 一个Object对象

wait和notify方法只能用于synchronized同步代码块内

synchronized的内存语义

volatile不同

进入synchronized块的内存语义是把再synchronized块内使用到的所有共享变量从栈内存中清空,这样就只能从堆内存只读取,保证了内存可见性。退出synchronized块的内存语义是把synchronized块内对共享变量的修改刷新到堆内存。

仔细想想,这其实也是一个加锁和解锁的过程,保证共享变量修改的可见性。

总结

  • volatile仅能保证单个共享变量内存的可见性,不能保证原子性。而synchronized既可保证同步块内所有共享变量的内存可见性,又能保证其操作的原子性。

  • volatile是一个轻量级的保证内存可见性的关键字,实际上并没有加锁。因此它的性能很高。

  • synchronized是一个重量级的锁,可以用在代码块、普通方法以及静态方法上。用在代码块时锁就是synchronized(~)内的对象,用在普通方法时锁就是this,用在静态方法时锁就是this.getClass()

  • synchronized保证同步块内代码的原子性,因为要进行线程上下文切换,性能较低。不过优化过后性能也还可以。

参考

浅谈synchronized和volatitle实现线程安全的策略的更多相关文章

  1. 浅谈synchronized

    目录 浅谈synchronized 前言 是什么 格式 同步代码块 同步方法 注意 最后 浅谈synchronized 前言 看多线程的相关书籍的时候,会经常阅读到一个使用前景,就是银行的取钱存钱操作 ...

  2. Java多线程编程(四)—浅谈synchronized与lock

    一.共享资源竞争问题 在Java语言的并发编程中,由于我们不知道线程实际上在何时运行,所以在实际多线程编程中,如果两个线程访问相同的资源,那么由于线程运行的不确定性便会在这种多线程中产生访问错误.所以 ...

  3. 浅谈Java线程安全

    浅谈Java线程安全 - - 2019-04-25    17:37:28 线程安全 Java中的线程安全 按照线程安全的安全程序由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类 ...

  4. 对kotlin和java中的synchronized的浅谈

    synchronized在java中是一个关键字,但是在kotlin中是一个内联函数.假如分别在java和kotlin代码锁住同一个对象,会发生什么呢,今天写了代码试了试.首先定义people类 12 ...

  5. 浅谈线程池(中):独立线程池的作用及IO线程池

    原文地址:http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html 在上一篇文章中,我们简单讨论了线程池的 ...

  6. 浅谈线程池(上):线程池的作用及CLR线程池

    原文地址:http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html 线程池是一个重要的概念. ...

  7. 转载【浅谈ThreadPool 线程池】

    浅谈ThreadPool 线程池 http://www.cnblogs.com/xugang/archive/2010/04/20/1716042.html

  8. 浅谈ThreadPool 线程池(引用)

    出自:http://www.cnblogs.com/xugang/archive/2010/04/20/1716042.html 浅谈ThreadPool 线程池 相关概念: 线程池可以看做容纳线程的 ...

  9. 【分析】浅谈C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别(SamWang)

    [分析]浅谈C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别(SamWang) 今天无意中看到有关Invoke和BeginInvoke的一些资料,不太清楚它们之间 ...

随机推荐

  1. web前端学习笔记(python)(一)

    瞎JB搞]感觉自己全栈了,又要把数据库里面的内容,以web形式展示出来,并支持数据操作.占了好多坑.....慢慢填(主要参考廖雪峰的官网,不懂的再百度) 一.web概念 Client/Server模式 ...

  2. 后端程序员之路 14、NumPy

    NumPy - NumPyhttp://www.numpy.org/ NumPy-快速处理数据 - 用Python做科学计算http://old.sebug.net/paper/books/scipy ...

  3. 探索 .NET Core 依赖注入的 IServiceProvider

    在上一篇文章中,我们学习了Microsoft.Extensions.DependencyInjection中的IServiceCollection,包括服务注册转换为ServiceDescriptor ...

  4. 最简单的,在win,linux中,用powershell,自动获取Let's Encrypt证书方法

    powershell传教士原创 2020-04-12 Let's Encrypt证书有效期3个月,支持泛域名[*.你的网站.net].支持n天内(一般10天内就够用了),用脚本自动续期. 简介: 这个 ...

  5. HDOJ-6628(dfs+第k字典序最小差异序列)

    permutation 1 HDOJ-6628 这题使用的暴力深搜,在dfs里面直接从最小的差异开始枚举 注意这里的pre记录前一个数,并且最后答案需要减去排列中最小的数再加一 这里有一个技巧关于求第 ...

  6. 《Selenium自动化测试实战》新书上市,有需要朋友们可以了解下,欢迎大家多提宝贵意见

    京东:https://item.jd.com/13123910.html当当:http://product.dangdang.com/29204520.html 1. 本书基于 Python 3.8 ...

  7. 测试平台系列(2) 给Pity添加配置

    给Pity添加配置 回顾 还记得上篇文章创立的「Flask」实例吗?我们通过这个实例,给根路由 「/」 绑定了一个方法,从而使得用户访问不同路由的时候可以执行不同的方法. 配置 要知道,在一个「Web ...

  8. Matplotlib图例中文乱码

    plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签 plt.rcParams['axes.unicode_minus']=False #用来正 ...

  9. 一个C#开发编写Java框架的心路历程

    前言 这一篇絮絮叨叨,逻辑不太清晰的编写Java框架的的一个过程,主要描述我作为一个java初学者,在编写Java框架时的一些心得感悟. 因为我是C#的开发者,所以,在编写Java框架时,或多或少会带 ...

  10. 世界国省市区SQL语句(mysql)

    CREATE TABLE loctionall ( country VARCHAR(40) , provice VARCHAR(40) , city VARCHAR(40) , CONSTRAINT ...