final域、Atomic和ThreadLocal

解决并发2种方式

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

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

final

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

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

写final域重排序规则

  • JMM禁止编译器把final域的写重排序到构造函数之外。
  •  编译器会在final域的写之后,构造函数return之前,插入一个 StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造 函数之外

读域的重排序规则

  • 在一个线程中,初次读对象引用与初次读该对象包含的final 域,JMM禁止处理器重排序这两个操作,编译器会在读final 域操作的前面插入一个LoadLoad屏障。

Atomic(无锁工具的典范)

原子性问题解决

  • synchronized、Lock
  • J.U.C包下的Atomic类

Atomic实现原理  参考这篇

调用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

如AtomicInteger

  • 构造函数,把数值放进成员变量中,value声明为volatile类型
    public AtomicInteger(int initialValue) { 
       value = initialValue;
    }
  • value的值通过内部this和valueOffset找到地址进行CAS操作

ABA问题

解决办法:加入版本号或时间戳等标志,如使用AtomicMarkableReference,AtomicStampedReference

由于compareAndSet只能一次改变一个值,无法同时改变newReference和newStamp,所以在实现的时候,在内部定义了一个类Pair类将newReference和newStamp变成一个对象,进行CAS操作的时候,实际上是对Pair对象的操作,Pair有2个成员变量: T reference; int stamp;

public boolean compareAndSet(V  expectedReference, V  newReference, 
                            int expectedStamp,int newStamp) {
    Pair<V> current = pair;
    return  expectedReference == current.reference && 
            expectedStamp == current.stamp &&
            ((newReference == current.reference && newStamp == current.stamp) || 
            casPair(current, Pair.of(newReference, newStamp)));
}

 Atomic包中的类按照操作的数据类型可以分成4组, 我们一般常用的AtomicInteger、AtomicReference和AtomicStampedReference

  •    AtomicBoolean,AtomicInteger,AtomicLong

         线程安全的基本类型的原子性操作

  •    AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

         线程安全的数组类型的原子性操作,它操作的不是整个数组,而是数组中的单个元素

  •    AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

         基于反射原理对象中的基本类型(长整型、整型和引用类型)进行线程安全的操作

  •    AtomicReference ,AtomicMarkableReference,AtomicStampedReference

         线程安全的引用类型及防止ABA问题的引用类型的原子操作

 在LongAdder 与AtomicLong有区别
Atomic*(CAS+自旋)遇到的问题是,用于低并发场景。

LongAddr(CAS乐观锁保证原子性,通过自旋保证当次修改的最终修改成功,通过降低锁粒度(多段锁))加了分段锁,当竞争不激烈的时候,所有线程都是通过CAS对同一个变量(Base)进行修改,当竞争激烈的时候,会将根据当前线程哈希到对于Cell上进行修改(多段锁)

ThreadLocal的使用和原理 参考https://blog.csdn.net/qq_34039868/article/details/89017189?utm_source=app&app_version=4.5.0

内存泄露(简单说就是弱引用自动没了,即key为null,但线程还在,则value还在就会oom),参考https://blog.csdn.net/u012088516/article/details/84067841?utm_source=app&app_version=4.5.0

是什么

ThreadLocal用于保存某个线程共享变量,是线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。

为什么

我们知道,当在普通方法中创建一个变量类,若没有特别在方法区域外留有该类的引用,当方法结束后在其它地方不能够再使用这个类。当我们在其它地方还要用到方法中创建的对象时,我们通常会用一个全局变量指向这个对象,这样在整个项目中都能再访问这个对象了。但想想,在多线程情况下,每个线程访问该方法都会创建一个全局对象,在高并发下那我们岂不是要创建成千上万个全局变量来存?若该对象还带有每个线程特有的参数,那就要保证每个线程在之后能调用自己的创建的对象,一般情况下是很难进行管理的(我的想法是加锁,不加锁就用threadlocal)。而ThreadLocal就做到了既能让对象在其它地方被创建线程访问,也省去了自己管理全局对象的麻烦。(自己管理全局对象,不是很理解这句话,大概我猜测是交给map管理)

一般来讲,在单线程应用程序中可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法(save,get等)时都要传递一个Connection对象。由于Jdbc连接对象不一定是线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的,比如线程一刚获取全局的Connection,准备进行数据库操作,但线程二却执行了Connection.close()。

这种情况下我们可能会取消Connection这个全局变量,在每次要进行数据库相关操作时直接new一个Connection对象进行连接,而这又会导致一个线程执行多次数据库操作时要new多个connection对象,加大系统的负担。这时就会想能不能有这样一种方法,既不让Connection成为全局变量来保证线程安全,又可以实现全局Connection带来的“一次连接”式的便利?ThreadLocal就是做这件事的。

总理:threadlocal解决让对象在其它地方被创建的线程访问

֯使用场景

 常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。 

怎么用 

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
/*可以是string,也可以是list,我的理解(可以把ThreadLocal看作是当前线程的一个附加的一个属性)
private static ThreadLocal<String> threadLocal= new ThreadLocal<>();
private static ThreadLocal<List<String>> threadLocal= new ThreadLocal<list<String>>();
*/
 
connectionHolder.set(DriverManager.getConnection(DB_URL));
 
connectionHolder.get();

 第一行代码new了一个静态的ThreadLocal<Connection>。以后只要让每个线程在进行jdbc操作前都执行第二行代码,就会把一个创建的Connection对象存放到当前线程的map容器中(ThreadLocalMap,见下文介绍),后面该线程要进行数据库操作时只要执行第三行代码,就能拿到这个引用进行连接,不用每次都new一个新的。

你可能会问只是一句简单的connectionHolder.get()代码,而且ThreadLocal对象只有一个,那怎么能准确的拿到当前线程中的Connection呢?答案就是:这个connection是存在当前线程(一个Thread)中的,不是ThreadLocal中。表面上调用的ThreadLocal.get(),实际上是在当前线程对象的Map容器中进行设置和查找。不过当前线程的map容器中可能会存多个ThreadLocal的值,所以Map中的key就是ThreadLoca对象,值就是connection,来进行区分。

原理简要分析,详情可以看上面的参考博文

thread类有个属性

public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

 ThreadLocalMap 是一个map,用数组实现 Entry[] table

 static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
......

 ThreadLocalMap 有个class Entry,就相当于是个键值对对象,key是父类WeakReference中的referent,也就是当前线程的一个ThreadLocal实例对象,value ThreadLocal是set传入的值,每次set就是往 Entry[] table中加一个Entry对象,数组默认大小16

//根据ThreadLocal的hash值计算应该从table中哪个位置开始(跟HashMap一样),key是传入参数ThreadLocal,threadLocalHashCode 是ThreadLocal一个属性。即 threadLocal.threadLocalHashCode & (len-1);
int i = key.threadLocalHashCode & (len-1);

ThreadLocalMap的set方法 

1.根据传入的ThreadLocal的hash值计算应该从table中哪个位置开始

2.找到entry从而得到key,比较key是否相同。如果找到相同的key,覆盖并返回,如果发现了空key为空,即ThreadLocal对象被GC了,就把数据放到该位置

3.当返现entry==null时,new再检测是否扩容

内部组成

  • Thread类中有个变量threadLocals,这个类型为ThreadLocal中的一个内部类ThreadLocalMap,这个类没有实现map接口,就是一个普通的Java类,但是实现的类似map的功能。ThreadLocalMap是ThreadLocal的内部类,每个数据用Entry保存,其中的Entry继承与WeakReference

哈希冲突:

ThreadLocal中的hash code非常简单,就是调用AtomicInteger的getAndAdd方法,参数是个固定值0x61c88647。ThreadLocalMap的结构非常简单只用一个数组存储,并没有链表结构,当出现Hash冲突时采用线性查找的方式,所谓线性查找,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。如果产生多次hash冲突,处理起来就没有HashMap的效率高,为了避免哈希冲突,使用尽量少的threadlocal变量

注:一个ThreadLocal只能保存一个变量的副本,如果需要多个,就得创建多个变量;我们确定使用完需要执行remove避免内存泄漏,使用 ThreadLocal 的时候,最好要声明为静态的

简单说说get方法

主要逻辑如下:

  • 先获取当前线程t;
  • 然后获取ThreadLocalMap;
  • 如果map不为空,则以当前对象(ThreadLocal对象)为key获取value;
  • 如果map为空,则执行初始化操作;

如果是第一次调用get时ThreadLocalMap如果还没有的话是如何初始化,如下

setInitialValue的主要逻辑如下:

  • 首先通过initialValue方法生成初始值;
  • 然后获取ThreadLocalMap;
  • 如果map不为空,则将第1步生成的值set进去,以当前对象(ThreadLocal对象)为key;
  • 如果map为空,则new一个ThreadLocalMap出来;
  • 返回生成的初始值;

ThreadLocal结构图

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值