【java基础 14】锁的粒度:ThreadLocal、volatile、Atomic和Synchronized

原创 2017年01月07日 16:46:44

导读:题目中提到的几个关键字,分别是解决并发问题中,加锁所使用到的几个关键字,每个关键字代表的锁的粒度 不同,本篇博客,主要是从概念定义上,区分这几个关键字的应用场景。(PS:睡梦中,依稀记得有回面试的时候,问了我一个问题:你们在加锁的时候,加多大的锁? 哇塞,当时愣了一下,压根儿就没有这个大小的概念,我真的以为都是一样的)


话说,就像加锁日记本的锁是个很小的艺术锁,保险箱一般是密码锁(或者什么指纹人脸瞳孔识别之类的),锁铁门的一般都是那种大号锁,所以,仅从生活考虑,这个锁也是分大小的,唉,为毛自己写代码的时候,却没有注意过,不过,应该说,我至今为止,只用过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天,就废在这几个关键字上,我勒个去。然后还有关于信号量的问题还没有解决,好多好多。。。。。。


版权声明:每天进步一点点——我也只是写着玩儿——随便转随便评

相关文章推荐

ThreadLocal 和 synchornized 两者的区别

package com.itheima.hello.runnable; import java.util.Random; public class ThreadDemo1 implements R...

ThreadLocal 与 Synchronized 总结

1.对于同步方法和对象: 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的不同对象访问。 解释一下“取得的...

并发编程之ThreadLocal、Volatile、synchronized、Atomic关键字扫盲

前言对于ThreadLocal、Volatile、synchronized、Atomic这四个关键字,我想一提及到大家肯定都想到的是解决在多线程并发环境下资源的共享问题,但是要细说每一个的特点、区别、...

线程学习总结(volatile、synchronized、ThreadLocal)

1、线程内存访问机制         当线程访问某个对象的值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对...

锁的粒度:ThreadLocal、volatile、Atomic和Synchronized

导读:题目中提到的几个关键字,分别是解决并发问题中,加锁所使用到的几个关键字,每个关键字代表的锁的粒度 不同,本篇博客,主要是从概念定义上,区分这几个关键字的应用场景。(PS:睡梦中,依稀记得有回面试...

volatile、ThreadLocal、synchronized等3个关键字区别

1.volatile volatile主要是用来在多线程中同步变量。 在一般情况下,为了提升性能,每个线程在运行时都会将主内存中的变量保存一份在自己的内存中作为变量副本,但是这样就很容易出现多个线程...

synchronized同步引发的思考

最近公司某同事非常爱学,下班回家后也会抱书学习,看到多线程写例子的时候遇到了非常奇怪的问题,故而将例子发给我看让给解答,下面给出例子。  1.第一例及运行结果    下面是示例代码package co...

Java细粒度锁实现的3种方式

1. 分段锁 借鉴concurrentHashMap的分段思想,先生成一定数量的锁,具体使用的时候再根据key来返回对应的lock。这是几个实现里最简单,性能最高,也是最终被采用的锁策略,代码如...

Google Guava Striped 实现细粒度锁

首先不谈Striped能做什么,我们来看下如下的代码 /**   * 购买产品   * @param user 用户   * @param buyAmount 购买金额   * @param prod...

JAVA细粒度、互斥KEY锁 —— KeyLock

java中的几种锁:synchronized,ReentrantLock,ReentrantReadWriteLock已基本可以满足编程需求,但其粒度都太大,同一时刻只有一个线程能进入同步块,这对于某...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:【java基础 14】锁的粒度:ThreadLocal、volatile、Atomic和Synchronized
举报原因:
原因补充:

(最多只允许输入30个字)