本文共 2856 字,大约阅读时间需要 9 分钟。
现阶段项目中高并发越来越多,出现问题后bug也不好浮现,所以并发编程必须谨慎。
解决并发2种方式
对于并发工作,需要某种方式来防止两个任务同时访问相同的资源,至少在关键阶段不能出现这种冲突情况。并发中这几个关键字或者类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域,编译器和处理器要遵守两个重排序规则
JMM禁止编译器把final域的写重排序到构造函数之外。
final修饰大多用于基本数据类型域,或不可变类的域(如果类中的每个方法都不会改变其对象,这种类就是不可变类。例如,String类就是一个不可变的类)。对于可变的类使用final修饰符可能会对读者造成混乱。
调用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并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。 通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。 ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。 概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。转载地址:http://qhxzb.baihongyu.com/