【java基础 14】锁的粒度:ThreadLocal、volatile、Atomic和Synchronized
2017-01-07 16:47
639 查看
导读:题目中提到的几个关键字,分别是解决并发问题中,加锁所使用到的几个关键字,每个关键字代表的锁的粒度 不同,本篇博客,主要是从概念定义上,区分这几个关键字的应用场景。(PS:睡梦中,依稀记得有回面试的时候,问了我一个问题:你们在加锁的时候,加多大的锁? 哇塞,当时愣了一下,压根儿就没有这个大小的概念,我真的以为都是一样的)
话说,就像加锁日记本的锁是个很小的艺术锁,保险箱一般是密码锁(或者什么指纹人脸瞳孔识别之类的),锁铁门的一般都是那种大号锁,所以,仅从生活考虑,这个锁也是分大小的,唉,为毛自己写代码的时候,却没有注意过,不过,应该说,我至今为止,只用过final和Synchronized !好了,看下面的解析吧,话说多了都是泪!
首先,锁的粒度支持,也就是对Load、Store的各种顺序控制,load、store两两组合为4种情况:LoadLoad、StoreStore、LoadStore、StoreLoad,他们以一种指令屏障的方式来控制顺序。绝大部分系统,都支持StoreLoad。
备注:JVM中一些普通变量的操作指令
1,Load操作(将本地变量推至栈顶,用来给CPU调度运算)发生在read之后(之间可以有其他的指令)
2,普通变量的修改未必会理解发生Store操作(将栈顶的数据写入本地变量),但发生Store操作,就会发生write操作
理解:这就是一个线程安全的全局变量,呵呵,全局变量,就是动一个地方,到处都变了的那位,再代码设计比较乱的情况下,如果用了很多ThreadLocal,那这个系统就会慢慢的神龙见首不见尾,要是再整出一个bug,就真的呵呵了。 要点:ThreadLocal是一种对象持有的方式,每个线程都有一个ThreadLocalMap,而这个ThradLocal则相当于是一个Key值,要保持的对象作为value值。
ThreadLocal的“坑”:
1,ThreadLocal是一个与线程绑定的变量,所以说,如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将与线程共存,第一个坑:不知道它的作用域范围
2,理论上说,线程结束后ThreadLcoal就会被回收,但事实可能并非如此。比如说,在线程池中对线程管理都是采用线程复用的方法,在线程池中线程很难结束甚至永远不会结束,第二个坑:ThreadLocal变量的生命周期不可预测
3,理论上每次set数据时,使用ThreadLocal本身作为Key,相同的Key肯定会替换原来的数据,原来的数据就会被释放,但是,如果ThreadLocal中直接或间接包装了集合类或复杂对象,每次从同一个ThreadLocal中取出对象,再对内容进行操作,第三个坑:内部的集合类和复杂对象所占用的空间膨胀
填坑tips:让ThreadLocal的入口和出口可控,用finally去remove数据,为了不破坏ThradLocal的入口,一般在使用之前,会调用get()方法判断是否为null
为什么说ThradLocal是与线程绑定的(java7源码),在jThreadLocal类中,主要包含三个方法:set(),get(),remove()
备注:在任何异步程序中(包括异步I / O,非阻塞I / O),ThreadLocal的参数传递都是不靠谱的,因为线程将请求发送后,就不再等待远程返回结果继续向下执行了,真正的返回结果得到后,处理的线程可能是另一个。
volatile要求在对变量进行读\ 写操作时,其前后的指令在某些情况下不允许进行重排序,这种限制主要体现在以下3中情况:
1,如果是一条对volatile变量进行赋值操作的代码,那么在该代码前面的任何代码不能与这个赋值操作交换顺序;如果是一条读取volatile变量的代码,则正好相反
2,普通变量的读写操作相互之间是可以重排序的,只要不影响它们之间的逻辑语义顺序就可以重排序,但是如果普通变量的读\ 写操作遇上了volatile变量的操作,就需要遵循前一个基本原则
3,如果两个volatile变量的读/ 写操作都在一段代码中,则依然遵循前两个基本原则,此时不管两者之间的读\ 写顺序如何,都肯定不会重排序
理解:其实说到这个volatile变量,就想到那个sql,就是说发出sql查询语句,只会获取到发出sql语句时的数据块数据,而不会获取到之后的数据。比如说,我在9点1分1秒发出了一条查询语句,然后在9点1分2秒执行了update操作,那么我拿到的数据,只会是update之前的数据。这个volatile变量,感觉就是一样的,如果我有一个线程正在读取这个变量,那么另一个写操作的线程,就必须等待我这个读操作结束,因为这个volatile不允许重排序!
对于这一个,结合到自己在项目中应用,我只想问我自己一个问题:姑娘,你难道只会把Synchronized关键字加到方法块吗?我要反思的是,为什么我加锁的最小粒度是一个完整的方法?其实,很多时候,这个锁的范围真的变大了,频繁的锁征用,就进入了悲观锁的状态!在加锁的时候,应该注意一下锁的粒度问题,节省不必要的开销!
Atomic为我们提供了一些列java原子变量的操作方法,其中,Atomic提供的原子操作类有(java 7源码):
![](http://img.blog.csdn.net/20170107155206463?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGh4MDYyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
可分为4中类型:
1,基本变量操作:Boolean,Integer,Long
2,引用(reference)操作:也就是对象引用的操作,可以做到原子级别,当多个线程对同一个引用发生修改时,只会有一个成功(存在对ABA问题的处理)
3,数组(Array)操作:这个操作并不是操作数组的对象,而是数组中的每一个元素,针对每一个元素的读写操作是线程安全的
4,Updater:java提供了一种updater机制,可以在原有的类定义volatile变量的基础上,实现一种原子性的管理,而不需要将变量本身定义为Atomic,这样可以在不破坏原有程序逻辑的基础上实现一致性的读写
简单说来,它的原子性实现,是基于可见性,修改前提取、修改后对比来确定是否写回到主存。请看下面的关键代码,以AtomicInteger为例(其他的几个操作类都类似,java7源码):
实现原子性,主要就是两种方法,一种是总线加锁,也就是我们常说的悲观锁;另外一种就是缓存加锁,对应着乐观锁
以后自己再处理并发的时候,对于锁的概念,估计能清楚点。写这篇博客,看了书、参考了博客、还看了源码,我也真是够了!我足足写了差不多1天,就废在这几个关键字上,我勒个去。然后还有关于信号量的问题还没有解决,好多好多。。。。。。
话说,就像加锁日记本的锁是个很小的艺术锁,保险箱一般是密码锁(或者什么指纹人脸瞳孔识别之类的),锁铁门的一般都是那种大号锁,所以,仅从生活考虑,这个锁也是分大小的,唉,为毛自己写代码的时候,却没有注意过,不过,应该说,我至今为止,只用过final和Synchronized !好了,看下面的解析吧,话说多了都是泪!
首先,锁的粒度支持,也就是对Load、Store的各种顺序控制,load、store两两组合为4种情况:LoadLoad、StoreStore、LoadStore、StoreLoad,他们以一种指令屏障的方式来控制顺序。绝大部分系统,都支持StoreLoad。
备注:JVM中一些普通变量的操作指令
1,Load操作(将本地变量推至栈顶,用来给CPU调度运算)发生在read之后(之间可以有其他的指令)
2,普通变量的修改未必会理解发生Store操作(将栈顶的数据写入本地变量),但发生Store操作,就会发生write操作
一、ThreadLocal
ThreadLocal可以放一个线程级别的变量,但是它本身可以被多个线程共享使用,而且又可以达到线程安全的目的,且绝对线程安全理解:这就是一个线程安全的全局变量,呵呵,全局变量,就是动一个地方,到处都变了的那位,再代码设计比较乱的情况下,如果用了很多ThreadLocal,那这个系统就会慢慢的神龙见首不见尾,要是再整出一个bug,就真的呵呵了。 要点:ThreadLocal是一种对象持有的方式,每个线程都有一个ThreadLocalMap,而这个ThradLocal则相当于是一个Key值,要保持的对象作为value值。
ThreadLocal的“坑”:
1,ThreadLocal是一个与线程绑定的变量,所以说,如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将与线程共存,第一个坑:不知道它的作用域范围
2,理论上说,线程结束后ThreadLcoal就会被回收,但事实可能并非如此。比如说,在线程池中对线程管理都是采用线程复用的方法,在线程池中线程很难结束甚至永远不会结束,第二个坑:ThreadLocal变量的生命周期不可预测
3,理论上每次set数据时,使用ThreadLocal本身作为Key,相同的Key肯定会替换原来的数据,原来的数据就会被释放,但是,如果ThreadLocal中直接或间接包装了集合类或复杂对象,每次从同一个ThreadLocal中取出对象,再对内容进行操作,第三个坑:内部的集合类和复杂对象所占用的空间膨胀
填坑tips:让ThreadLocal的入口和出口可控,用finally去remove数据,为了不破坏ThradLocal的入口,一般在使用之前,会调用get()方法判断是否为null
为什么说ThradLocal是与线程绑定的(java7源码),在jThreadLocal类中,主要包含三个方法:set(),get(),remove()
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } /** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } /** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * <tt>initialValue</tt> method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
备注:在任何异步程序中(包括异步I / O,非阻塞I / O),ThreadLocal的参数传递都是不靠谱的,因为线程将请求发送后,就不再等待远程返回结果继续向下执行了,真正的返回结果得到后,处理的线程可能是另一个。
二、volatile
volatile被称为是“最轻量级的锁”,因为它只是在读这个瞬间要求一个简单的顺序,而不是一个变量上的原子读写,或者在一段代码上的同步!volatile要求在对变量进行读\ 写操作时,其前后的指令在某些情况下不允许进行重排序,这种限制主要体现在以下3中情况:
1,如果是一条对volatile变量进行赋值操作的代码,那么在该代码前面的任何代码不能与这个赋值操作交换顺序;如果是一条读取volatile变量的代码,则正好相反
2,普通变量的读写操作相互之间是可以重排序的,只要不影响它们之间的逻辑语义顺序就可以重排序,但是如果普通变量的读\ 写操作遇上了volatile变量的操作,就需要遵循前一个基本原则
3,如果两个volatile变量的读/ 写操作都在一段代码中,则依然遵循前两个基本原则,此时不管两者之间的读\ 写顺序如何,都肯定不会重排序
理解:其实说到这个volatile变量,就想到那个sql,就是说发出sql查询语句,只会获取到发出sql语句时的数据块数据,而不会获取到之后的数据。比如说,我在9点1分1秒发出了一条查询语句,然后在9点1分2秒执行了update操作,那么我拿到的数据,只会是update之前的数据。这个volatile变量,感觉就是一样的,如果我有一个线程正在读取这个变量,那么另一个写操作的线程,就必须等待我这个读操作结束,因为这个volatile不允许重排序!
三、synchronized
Synchronized是一把锁,始终保证临界区的访问控制。临界区:指访问这个地方最多只能有一个线程在里面!对于这一个,结合到自己在项目中应用,我只想问我自己一个问题:姑娘,你难道只会把Synchronized关键字加到方法块吗?我要反思的是,为什么我加锁的最小粒度是一个完整的方法?其实,很多时候,这个锁的范围真的变大了,频繁的锁征用,就进入了悲观锁的状态!在加锁的时候,应该注意一下锁的粒度问题,节省不必要的开销!
四、Atomic
PS:想想事务的原子性,再想想乐观锁的实现原理,这个东西秒懂Atomic为我们提供了一些列java原子变量的操作方法,其中,Atomic提供的原子操作类有(java 7源码):
可分为4中类型:
1,基本变量操作:Boolean,Integer,Long
2,引用(reference)操作:也就是对象引用的操作,可以做到原子级别,当多个线程对同一个引用发生修改时,只会有一个成功(存在对ABA问题的处理)
3,数组(Array)操作:这个操作并不是操作数组的对象,而是数组中的每一个元素,针对每一个元素的读写操作是线程安全的
4,Updater:java提供了一种updater机制,可以在原有的类定义volatile变量的基础上,实现一种原子性的管理,而不需要将变量本身定义为Atomic,这样可以在不破坏原有程序逻辑的基础上实现一致性的读写
简单说来,它的原子性实现,是基于可见性,修改前提取、修改后对比来确定是否写回到主存。请看下面的关键代码,以AtomicInteger为例(其他的几个操作类都类似,java7源码):
/** * Atomically sets to the given value and returns the old value. * * @param newValue the new value * @return the previous value */ public final int getAndSet(int newValue) { for (;;) { int current = get(); if (compareAndSet(current, newValue)) return current; } } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }从代码中对compareAndSet方法的调用,可以知道,它每次修改,都会有一个期待值和当前值的对比,如果一致,则写入!
实现原子性,主要就是两种方法,一种是总线加锁,也就是我们常说的悲观锁;另外一种就是缓存加锁,对应着乐观锁
五、总结
关于这几个关键字的介绍就到这里了,简单说来,volatile和ThreadLocal主要是加在变量上,而Synchronized和Atomic是代码块或者更大级别的锁(Atomic可以不破坏原有程序的逻辑)以后自己再处理并发的时候,对于锁的概念,估计能清楚点。写这篇博客,看了书、参考了博客、还看了源码,我也真是够了!我足足写了差不多1天,就废在这几个关键字上,我勒个去。然后还有关于信号量的问题还没有解决,好多好多。。。。。。
相关文章推荐
- 【java基础 14】锁的粒度:ThreadLocal、volatile、Atomic和Synchronized
- 锁的粒度:ThreadLocal、volatile、Atomic和Synchronized
- Java——多线程总结及ThreadLocal、Volatile、synchronized、Atomic四个关键字
- 可能忽略的Java基础知识-小心使用synchronized和volatile
- Atomic、volatile、synchronized、ThreadLocal优缺点比较
- Atomic、ThreadLocal、Volatile、synchronized总结
- Java基础--ThreadLocal 与 Synchronized区别?
- 并发编程之ThreadLocal、Volatile、synchronized、Atomic关键字扫盲
- ThreadLocal、volatile、synchronized、Atomic关键字
- 【转】Java多线程编程中易混淆的3个关键字( volatile、ThreadLocal、synchronized)总结
- JAVA基础 (一) 并发 ThreadLocal与Synchronized 用哪一个好
- Java基础之volatile,static,synchronized的区别
- paip.提升性能----java 无锁结构(CAS, Atomic, Threadlocal, volatile, 函数式编码, 不变对象)
- 并发编程之ThreadLocal、Volatile、synchronized、Atomic关键字扫盲
- 并发编程之ThreadLocal、Volatile、synchronized、Atomic关键字扫盲
- (转)并发编程之ThreadLocal、Volatile、synchronized、Atomic关键字扫盲
- ThreadLocal、Volatile、synchronized、Atomic
- 并发编程之ThreadLocal、Volatile、synchronized、Atomic关键字扫盲
- Java并发基础(四)-volatile和synchronized
- JAVA多线程同步:volatile,synchronized,Atomic... 比较