解决并发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结构图