草庐IT

18 Java内存模型与线程_JVM同步机制和锁类库实现线程安全

拿了桔子跑-范德彪 2023-04-17 原文

目录

1 线程安全定义

含糊的定义:如果一个对象可以安全地被多个线程同时使用,那它就是线程安全的
严谨的定义:

当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。-----From《Java并发编程实战》作者Brian Goetz

2 Java数据与线程安全

从线程安全角度,将Java中各种操作共享的数据分为:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立

2.1 不可变

不可变的对象一定是线程安全。

如何保证不可变呢?

  1. 基本数据类型,用final修饰
  2. 对象类型:用final修饰对象中可变的字段

Java中常用的不可变对象:String、Number的部分子类如 Long、Double、BigInteger、BigDecimal等。

为什么String不可变,参考:Java基础类String学习分析

2.2 绝对线程安全

ConcurrentHashMap (我后续会更新ConcurrentHashMap源码分析专题)

2.3 相对线程安全

定义:只能保证对象单次的操作是线程安全,连续调用不能保证线程安全
大部分声称线程安全的类都属于这种类型,例如Vector、HashTable、Collections的 synchronizedCollection()方法包装的集合等。

2.4 线程兼容

定义:对象本身并不是线程安全的,但是可以通过在调用端使用同步手段来保证对象在并发环境中可以安全地使用。如集合类ArrayList和HashMap等。

2.5 线程对立

定义:不管调用端是否采取了同步措施,都无法在多线程环境中并发使用代码。
例子:Thread类的suspend()和resume()方法:如果有两个线程同时持有一个线程对象,一个尝试去中断线程,一个尝试去恢复线程,在并发进行的情况下,无论调用时是否进行了同步,目标线程都存在死锁风险

3 Java线程安全支持

JVM同步机制锁类库实现线程安全

3.1 互斥同步

同步:在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用
互斥:实现同步的一种手段。
互斥同步性能开销:互斥同步属于一种悲观的并发策略,无论共享的数据是否真的会出现竞争,它都会进行加锁,引发:用户态到核心态转换、维护锁计数器、检查是否有被阻塞的线程需要被唤醒 等开销

3.1.1 synchronized互斥同步原理

  1. synchronized关键字经过Javac编译之后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。
  2. 执行monitorenter指令时,首先要去尝试获取对象的锁。如果这个对象没被锁定,或者当前线程已经持有了那个对象的锁,就把锁的计数器的值增加1
  3. 执行 monitorexit指令时会将锁计数器的值减1,一旦计数器的值为零,锁随即就被释放
  4. 如果获取对象锁失败,那当前线程阻塞等待,直到锁被释放。

关于同步块的说明:
monitorenter和monitorexit指令,都需要一个reference类型的参数,指明要锁定和解锁的对象。synchronized修饰地方不同,reference取不同的值:

  • 修饰 对象,取这个对象的引用作为reference;
  • 修饰 实例方法,取方法所属对象实例作为reference,
  • 修饰 类方法,取Class对象来作为线程要持有的锁

根据两个monitorenter和monitorexit这两个字节码指令执行过程,可以得出以下推论:

  • 被synchronized修饰的同步块对同一条线程来说是可重入
  • 被synchronized修饰的同步块在持有锁的线程执行完毕并释放锁之前,会无条件地阻塞后面其他线程的进入

3.1.2 Lock接口和ReentrantLock互斥同步原理

参考:JUC锁: LockSupport详解 JUC锁: ReentrantLock详解 这两个专题讲解

3.1.3 synchronized和Lock对比

Lock应该确保在finally块中释放锁,否则一旦同步代码块中抛出异常,则有可能永远不会释放持有的锁。Lock必须由程序员来保证锁释放,而synchronized由Java虚拟机来确保即使出现异常,锁也能被自动释放。

3.2 非阻塞同步

非阻塞同步:乐观并发策略:不管风险,先进行操作,如果没有其他线程争用共享数据,那操作就直接成功了。如果共享的数据的确被争用,产生了冲突,那再进行其他的补偿措施,最常用的补偿措施是不断地重试,直到出现没有竞争的共享数据为止。乐观并发策略的实现不再需要把线程阻塞挂起

利用处理器指令,实现非阻塞同步

硬件保证某些从语义上看起来需要多次操作的行为可以只通过一条处理器指令就能完成,这类指令常用的有:

  • 测试并设置(Test-and-Set)
  • 获取并增加(Fetch-and-Increment)
  • 交换(Swap)
  • 比较并交换(Compare-and-Swap :CAS)
  • 加载链接/条件储存(Load-Linked/Store-Conditional:LL/SC)

CAS的专题分析:JUC原子类: CAS, Unsafe和原子类详解

3.3 无同步方案

如果方法不涉及共享数据,那自然不需要采用同步措施来保证其正确性。因此会有一些代码天生就是线程安全的

3.3.1 可重入代码

代码定义:可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误,也不会对结果有所影响。

特征:

  • 不依赖全局变量、堆数据、公用的系统资源
  • 用到的状态量都由参数中传入
  • 不调用非可重入的方法

3.3.2 线程本地存储

代码定义:共享数据保证只在同一个线程中执行
场景:消费队列的架构模式(如“生产者-消费者”模式)都会将产品的消费过程限制在一个线程中消费完。

如果变量要被多线程访问,使用volatile关键字将它声明为“易变的”;如果变量线程独享,通过java.lang.ThreadLocal类来实现线程本地存储的功能。

ThreadLocal专题分析:Threadlocal源码解读

有关18 Java内存模型与线程_JVM同步机制和锁类库实现线程安全的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - i18n Assets 管理/翻译 UI - 2

    我正在使用i18n从头开始​​构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在ruby​​onrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  5. ruby-on-rails - Rails 3 I18 : translation missing: da. datetime.distance_in_words.about_x_hours - 2

    我看到这个错误:translationmissing:da.datetime.distance_in_words.about_x_hours我的语言环境文件:http://pastie.org/2944890我的看法:我已将其添加到我的application.rb中:config.i18n.load_path+=Dir[Rails.root.join('my','locales','*.{rb,yml}').to_s]config.i18n.default_locale=:da如果我删除I18配置,帮助程序会处理英语。更新:我在config/enviorments/devolpment

  6. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  7. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  8. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  9. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  10. ruby-on-rails - 如何将验证与模型分开 - 2

    我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:

随机推荐