博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
volatile、atomic、final、ThreadLocal概述
阅读量:2169 次
发布时间:2019-05-01

本文共 2856 字,大约阅读时间需要 9 分钟。

 

现阶段项目中高并发越来越多,出现问题后bug也不好浮现,所以并发编程必须谨慎。

    解决并发2种方式

对于并发工作,需要某种方式来防止两个任务同时访问相同的资源,至少在关键阶段不能出现这种冲突情况。

  • 资源被一个任务使用时,在其上加锁
  • 根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。因此,如果你有5个线程都要使用变量x所表示的对象,那线程本地存储就会生成5个用于x的不同的存储块。它使得你可以将状态与线程关联起来。创建和管理线程本地存储可以由java.lang.ThreadLocal类来实现。
     

并发中这几个关键字或者类volatile、atomic、final、ThreadLocal经常被用到,拿到一起进行论述:

volatile域

volatite只保证线程在“加载数据阶段”加载的数据是最新的,并不能保证线程安全。

一个线程执行的过程有三个阶段:
加载(复制)主存数据到操作栈 -->  对操作栈数据进行修改  --> 将操作栈数据写回主存
volatite关键字,让编译器不去优化代码使用缓存等,以保证线程在“加载数据阶段”加载的数据都是最新的
比如:
某一时刻i=6是最新的值,volatile保证线程A,B都同时加载了这个最新的值,
然后A执行i(A)+1=7,然后将7写回主存,
B也执行i(B)+1=7,然后也将7写回内存,
这样,执行两次加法,i却只增加了1

      对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的,同时线程工作内存中的操作并不是原子性的。所以在一个线程对该变量进行操作的同时,其他的线程有可能也在对该变量进行操作。

        有时,仅仅为了读写一个或两个实例域就使用同步,显得开销过大了。

        多处理器的计算机能够暂时在该处理器内部的寄存器中保存内存中的值。结果是,运行在不同处理器上的线程可能在同一个内存位置取到不同的值。

        volatile关键字为实例域的同步提供了一种免锁机制,如果声明一个域为volatile,那么编译器和虚拟机就知道该域可能被另一个线程并发更新。

 

volatile强迫线程每次更改该域的值时都要写回内存,并且线程读取该域的值时也要从内存中去读。

 

 

final实例域

将实例域定义为final。构建对象时必须初始化这样的域。也就是说,必须确保在每一构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够在对他进行修改。这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变 。例如在Employee类中的name域声明为final,因为在对象构建之后,这个值不会再被修改,即没有setName方法。

 

换个角度对重排序理解fianal域,

对于final域,编译器和处理器要遵守两个重排序规则

  • 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一 个引用变量,这两个操作之间不能重排序。 
  • 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操 作之间不能重排序。
  • 写final域重排序规则

JMM禁止编译器把final域的写重排序到构造函数之外。

  •  编译器会在final域的写之后,构造函数return之前,插入一个 StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造 函数之外
  • 读域的重排序规则
  • 在一个线程中,初次读对象引用与初次读该对象包含的final 域,JMM禁止处理器重排序这两个操作,编译器会在读final 域操作的前面插入一个LoadLoad屏障。
     

final修饰大多用于基本数据类型域,或不可变类的域(如果类中的每个方法都不会改变其对象,这种类就是不可变类。例如,String类就是一个不可变的类)。对于可变的类使用final修饰符可能会对读者造成混乱。

atomic原子性(无锁工具的典范)

java.util.concurrent.atomic包中没有使用锁,使用了高效的机制保证一些操作的原子性, 如对基本类型和其包装类的更新。如可以使用AtomicIngeger提供的方法对一个 int型域增减操作来作为共享计数器而无需同步。

调用Unsafe类提供compareAndSwapObject、compareAndSwapInt、compareAndSwapLong等方法,用了CPU的系统原语(CPU的一个不能再分割指令),也就是CAS操作。AtomicStampedReference等用来解决ABA问题

unsafe类 是java提供的获得对对象内存地址访问的类, getIntVolatile(var1, var2) 获取线程间共享的变量。JNI的类,Unsafe里面的native方法直接操作内存,getUnfate()仅供高级的Bootstrap类加载器使用,其实就是直接操作CPU

AtomicInteger、AtomicLong的compareAndSet、AtomicReference基于unsafe.compareAndSwapInt和unsafe.compareAndSwapLong和unsafe.compareAndSwapObject
 

ThreadLocal线程的局部变量

线程间共享变量带来了风险,如发生死锁。有时可能要避免共享变量,使用ThreadLocal辅助类为各个线程提供各自的实例。

ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

 
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
 
通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
 
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
 
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
 

转载地址:http://qhxzb.baihongyu.com/

你可能感兴趣的文章
有道词典for mac在Mac OS X 10.9不能取词
查看>>
关于“团队建设”的反思
查看>>
利用jekyll在github中搭建博客
查看>>
Windows7中IIS简单安装与配置(详细图解)
查看>>
linux基本命令
查看>>
BlockQueue 生产消费 不需要判断阻塞唤醒条件
查看>>
强引用 软引用 弱引用 虚引用
查看>>
数据类型 java转换
查看>>
"NetworkError: 400 Bad Request - http://172.16.47.117:8088/rhip/**/####t/approval?date=976
查看>>
mybatis 根据 数据库表 自动生成 实体
查看>>
win10将IE11兼容ie10
查看>>
checkbox设置字体颜色
查看>>
第一篇 HelloWorld.java重新学起
查看>>
ORACLE表空间扩张
查看>>
orcal 循环执行sql
查看>>
web.xml配置监听器,加载数据库信息配置文件ServletContextListener
查看>>
结构型模式之桥接模式(Bridge)
查看>>
行为型模式之状态模式(State)
查看>>
行为型模式之策略模式(Strategy)
查看>>
行为型模式之模板方法模式(TemplateMethod)
查看>>