ThreadLocal

ThreadLocal

在这里插入图片描述

1. ThreadLocal 基本介绍

在处理多线程并发安全的方法中,最常见的方式就是加锁,通过锁来控制多个不同线程对临界区的访问。但是无论是乐观锁,还是悲观锁都会在并发冲突时对性能造成一定的影响。
有一种避免竞争的方式,就是通过ThreadLocal

1.1 什么是ThreadLocal?

提供线程内的局部变量,线程私有。不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用;

ThreadLocal不是考虑怎么解决线程冲突,而是直接避免线程冲突的发生;

1.2 ThreadLocalSynchronized 的区别

ThreadLocalSynchronized 关键字都用于处理多线程并发访问变量的问题;

ThreadLocalSynchronized
原理“空间换时间的方式”,每一个线程都提供一份变量的副本,不同线程同时访问互不干扰“时间换空间的方式”,只提供一份变量,让不同的线程排队访问
侧重点多线程之间每个线程之间的数据相互隔离多个线程之间访问同一个变量资源

ThreadLocal 相对 Synchronized 更优,能够使程序拥有更高的并发性;

1.3 ThreadLocal 的优点

  1. 传递数据:保存每个线程绑定的数据,在需要的地方可以直接获取,避免参数直接传递带来的代码耦合问题
  2. 线程隔离:每个线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失

1.4 ThreadLocal 使用示例

public class ThreadLocalDemo {
    private static ThreadLocal<String> threadLocal = new ThreadLocal();

    public static void main(String[] args) {

        for (int i = 0; i < 3; i++) {
            final int temp = i;
            Thread thread = new Thread(() -> {
                //1. 获取初始ThreadLocal值
                String o = threadLocal.get();
                System.out.println("初始状态 thread:"+temp+"get threadLocal value:"+o);
                System.out.println("thread:"+temp+"set threadLocal value:"+o);
                //2. 向ThreadLocal中设置值
                threadLocal.set(String.valueOf(temp));

                try {
                    //sleep等待其他线程也向ThreadLocal中设值完成
                    System.out.println("thread:"+temp+" begin sleep");
                    Thread.sleep(1000);
                    System.out.println("thread:"+temp+" end sleep");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String finalValue = threadLocal.get();
                System.out.println("最终状态: thread:"+temp+" get threadLocal value:"+finalValue);

            },"Thread"+i);
            thread.start();
        }
    }
}
运行结果:
初始状态 thread:2get threadLocal value:null
初始状态 thread:0get threadLocal value:null
thread:0set threadLocal value:0
初始状态 thread:1get threadLocal value:null
thread:1set threadLocal value:1
thread:0 begin sleep
thread:2set threadLocal value:2
thread:1 begin sleep
thread:2 begin sleep
thread:2 end sleep
thread:1 end sleep
thread:0 end sleep
最终状态: thread:1 get threadLocal value:1
最终状态: thread:2 get threadLocal value:2
最终状态: thread:0 get threadLocal value:0

可以看到,上述案例三个线程同时向 变量threadLocal中设值,但是我们从执行结果中可以看出,设置的值只在线程中生效,多个线程间互不影响;

1.4.1 ThreadLocal 的初始化withInitial()

从上面的案例可以看出,通过 new ThreadLocal() 方式初始化得到的 ThreadLocal是一个没有初始值的对象,那么我们怎样才能创建一个ThreadLocal对象,并且对象给每一个线程都能传递一个初始值呢?ThreadLocal 提供了一个withInitial()方法统一初始化所有线程;

public class ThreadLocalDemo2 {
    private static ThreadLocal<String> threadLocal =ThreadLocal.withInitial(()->"MockValue");
    private static ThreadLocal<String> threadLocal2 =new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "MorkValue2";
        }
    };

    public static void main(String[] args) {

        for (int i = 0; i < 3; i++) {
            final int temp = i;
            Thread thread = new Thread(() -> {
                //1. 获取初始ThreadLocal值
                String o = threadLocal.get();
                String o2 = threadLocal2.get();
                System.out.println("初始状态 thread:"+temp+"get threadLocal value:"+o);
                System.out.println("初始状态 thread:"+temp+"get threadLocal value:"+o2);

            },"Thread"+i);
            thread.start();
        }
    }
}
运行结果:
初始状态 thread:0get threadLocal value:MockValue
初始状态 thread:0get threadLocal value:MorkValue2
初始状态 thread:1get threadLocal value:MockValue
初始状态 thread:1get threadLocal value:MorkValue2
初始状态 thread:2get threadLocal value:MockValue
初始状态 thread:2get threadLocal value:MorkValue2

2. ThreadLocal 源码分析(JDK1.8

2.1 ThreadLocal 中的成员

public class ThreadLoacl<T>{
    
    /**
    * Entry 继承 WeakReference,并且使用ThreadLocal作为key,如果key为null,意味着不再被引用,代表	* 可从table中清除
    *
    * 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 ThreadLocalMap {
        //The initial capacity -- MUST be a power of two.
    	private static final int INITIAL_CAPACITY = 16;
        /**
        * 存放数据的table
        * The table, resized as necessary.
        * table.length MUST always be a power of two.
        */
        private Entry[] table;
        /**
        * The number of entries in the table.
        */
        private int size = 0;
        /**
        * 进行扩容的阈值,使用量大于阈值需要进行扩容
        * The next size value at which to resize.
        */
        private int threshold; // Default to 0
        
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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


2.2 get()方法


    /**
     * 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() {
        //获取当前线程
        //每一个线程都有一个独立的 ThreadLocalMap,ThreadLocalMap 中有多个 ThreadLocal
        Thread t = Thread.currentThread();
        //当前线程作为参数,获取当前线程的 ThreadLocalMap.(当前线程第一次获取 Map值为空值)
        //所以第一次获取Map时,会调用初始化方法 setInitalValue();
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null) {
            //这里的this对象指的是,调用get方法的ThreadLocal对象。
            //ThreadLocalMap中存储是以 threadLocal 对象作为键。
            ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocal.ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
    }
    

2.3 set()方法

public class ThreadLocal<T> {
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    static class ThreadLocalMap {
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            //1. 哈希计算位置
            int i = key.threadLocalHashCode & (len-1);
			//取出待放位置的节点
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                //4. nextIndex:若是Entry存在,但是key不为null而且key不等于传入的key,这种情况下(下面`for的代码`不会return结束),则代表发生了Hash冲突的情况;ThreadLocalMap采用线性探测法(nextIndex())来解决Hash冲突
                
                ThreadLocal<?> k = e.get();
				//2. 当key==Entry节点上的k,进行值的替换
                if (k == key) {
                    e.value = value;
                    return;
                }
				//3. 当节点上的键为空
                if (k == null) {
                    //
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            //5. 判断是否需要扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        /**
    	* Increment i modulo len.
    	*/
    	private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
        
        /**
         * Re-pack and/or re-size the table. First scan the entire
         * table removing stale entries. If this doesn't sufficiently
         * shrink the size of the table, double the table size.
         */
        private void rehash() {
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                //扩容
                resize();
        }
        
        /**
         * 假设当前table长度为16.也就是说如果计算出来的key的Hash值为14,
         * 如果table[14]上已经有值,并且其key与当前key不一致,
         * 那么就发生了hash冲突,这个时候,调用nextIndex将14加1得到15,
         * 取table[15]进行判断,这个时候如果还是冲突会回到0,取table[0],
         * 依此类推,直到可以插入为止;
         *
         * 按照上面的描述,可以把Entry[] table 看作是一个环形数组
         */
        private static int nextIndex(int i, int len) {
           return ((i + 1 < len) ? i + 1 : 0);
        }
        
    }

}

set() 执行流程

  1. 根据key计算i,找出i位置上的Entry
  2. 若是Entry已经存在,并且key等于传入的key,那么直接给这个Entry赋新的value值;
  3. 若是Entry存在,但是keynull,则调用replaceStaleEntry() 来更换这个key为空的Entry
  4. 若是Entry存在,但是key不为null而且key不等于传入的key,则调用nextIndex()
  5. 最后调用cleanSomeSlots()和判断容量是否超过扩容的阈值来决定是否需要调用rehash();(rehash中会进行判断是否需要扩容)

2.4 不同JDK版本下ThreadLocal的差异

1. 早期ThreadLocal结构设计

每个ThreadLocal 类都创建一个Map,然后用线程ID threadID 作为 MapKey,要存储的局部变量作为 Mapvalue,达到每个线程的局部变量隔离的效果;

看了jdk1.6jdk1.7ThreadLocal,发现和JDK8ThreadLocal没啥区别,但是网上确实有早期结构不同的说法,所以这个地方存疑;

在这里插入图片描述

2. JDK8 之后的结构

每个 Thread 维护一个 ThreadLocalMap 哈希表,这个哈希表的 keyThreadLocal 实例本身,value为真正要存储的值 Object

在这里插入图片描述

3. 数据结构迭代后的优点

  1. JDK8后每个Map存储的Entry数量给变少(减少Hash冲突)
  2. Thread销毁的时候,ThreadLocalMap 也会随之销毁,减少内存的占用

2.5 ThreadLocalHashMap的比较

HashMap 中采用的是链表法来处理冲突的。当 slot 节点上发生冲突了,冲突的元素会构成一个链表,放在同一个槽位上。
ThreadLocalMap 采用线性探测法(nextIndex())的方式,待插入的元素发现插入的位置上已经存在元素了,那么就会往后移动一个槽位插入。

在这里插入图片描述

3. 引用和内存泄露

public class ThreadLocalDemo3 {
    private static ThreadLocal tl = new ThreadLocal(); 
    public static void main(String[] args) {
        tl.set("testValue");
        tl = null;
    }
}
static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
			//key就是一个弱引用
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
}

在这里插入图片描述

3.1 弱引用和内存泄漏

java 中的四种引用

弱引用:不管对象有没有被引用,只要垃圾回收开始工作,就会回收对象

JDK8ThreadLocalMap 中key使用的是弱引用,可以看见堆内存中的 new ThreadLocal() 被两个地方有使用到,当我们代码中 tl = null ;时,只是断开了 tl ==>new ThreadLocal() 这一条链路,ThreadLocalMap 中还有一条弱引用指向着 new ThreadLocal();但是这条引用仅仅只是一条弱引用,这意味着当垃圾回收开始工作时,依然能够回收 new ThreadLocal() 的堆内存;
从上可知,弱引用时,new ThreadLocal() 的堆内存能够被回收,可以回收那么是否就意味着这种情况下不会造成内存泄漏的情况呢?
不是,虽然new ThreadLocal()能够被垃圾回收器回收,当时我们可以看到 Entry.value 依然有堆空间的占用,两者之间的连接采用的是强引用。这时依然会发生内存泄漏。所以我们在使用完 threadLocal之后,要调用 remove() 方法来防止内存泄漏的发生;

ThreadLocalMap 中使用的KeyThreadLocal 的弱引用,而value 是强引用。所以 ThreadLocal 没有在被外部强引用的情况下,在垃圾回收时,key 会被清理掉。而 value 不会被清理掉,这样一来,ThreadLocalMap 中 就会出现 key 为null 的Entry。如果我们不做任何的措施,那么Value 就不会被 GC 回收。ThreadLocalMap 实现中已经考虑到了这种情况,在调用 set(),get(),remove()方法的时候,会清理掉key 为null 的记录。

3.1.1 什么情况下,Entry.value会被正常回收?

entry中value的调用链路是 Thread==>ThreadLocalMap==>entry==>value
只有当 Thread 被回收时,value 才有被回收的可能。但是对于线程池来说,大部分的线程会一直存在系统的整个生命周期中,那样就很难被回收。

3.1.2 ThreadLocal如何来解决内存泄漏问题的?

前面我们提到,ThreadLocalMap 实现中已经考虑到了这种情况,在调用 set(),get(),remove()方法的时候,会清理掉key 为null 的记录。那么我们看下源码是如何做的(以get() 方法为例)。

get()>map.getEntry(this)>getEntryAfterMiss(key, i, e)==>expungeStaleEntry(i)

	public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
	private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            //如果找到key,直接返回
            return e;
        else
            //如果找不到,就会尝试清理,如果你总是访问存在的key,那么这个清理永远不会进来
            return getEntryAfterMiss(key, i, e);
	}
	private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

             while (e != null) {
                // 整个e是entry ,也就是一个弱引用
                ThreadLocal<?> k = e.get();
                //如果找到了,就返回
                if (k == key)
                    return e;
                if (k == null)
                    //如果key为null,说明弱引用已经被回收了
                    //那么就要在这里回收里面的value了
                    expungeStaleEntry(i);
                else
                    //如果key不是要找的那个,那说明有hash冲突,这里是处理冲突,找下一个entry
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
    }	

真正用来回收value的是expungeStaleEntry()方法,在remove()和set()方法中,都会直接或者间接调用到这个方法进行value的清理;

3.2 强引用和内存泄漏

强引用:只要对象还在被使用,垃圾回收就不能回收此对象

由上可知,ThreadLocalMap 中key使用的是弱引用,这种情况会造成内存泄漏的情况,当我们采用强引用来关联key,还会造成内存泄漏么?
弱引用和内存泄漏分析可知,当我们使用强引用来关联时,不仅会造成 value值内存泄漏,还会造成key内存泄漏(强引用垃圾回收器无法进行垃圾回收);

3.3 内存泄漏的解决方案

threadLocal使用完后调用 remove()

4. ThreadLocal 的不继承性

public class ThreadLocalTest {

    private static ThreadLocal<String>  threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        threadLocal.set("mainValue");

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                String str = threadLocal.get();
                System.out.println("mainSonThread==>threadLocalValue:" + str);
            }
        },"mainSonThread");

        thread.start();
        thread.join();
		System.out.println( "main:" + threadLocal.get()) ;
    }
}
执行结果:
mainSonThread==>threadLocalValue:null
main:mainValue
**结论:同一个 ThreadLocal 变量在父线程中被设置值后,在子线程中是获取不到的。**

4.1 可继承的ThreadLocal(InheritableThreadLocal)

如何让子线程能访问到父线程中的值?可以使用被继承的ThreadLocal——InheritableThreadLocal

在实际开发过程中,我们可能会遇到这么一种场景。主线程开了一个子线程,但是我们希望在子线程中可以访问主线程中的ThreadLocal对象,也就是说有些数据需要进行父子线程间的传递。比如像这样:

public static void main(String[] args) {
    ThreadLocal threadLocal = new ThreadLocal();
    IntStream.range(0,10).forEach(i -> {
        //每个线程的序列号,希望在子线程中能够拿到
        threadLocal.set(i);
        //这里来了一个子线程,我们希望可以访问上面的threadLocal
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}

执行上述代码,你会看到:

Thread-0:null
Thread-1:null
Thread-2:null
Thread-3:null

因为在子线程中,是没有threadLocal的。如果我们希望子线可以看到父线程的ThreadLocal,那么就可以使用InheritableThreadLocal。顾名思义,这就是一个支持线程间父子继承的ThreadLocal,将上述代码中的threadLocal使用InheritableThreadLocal

InheritableThreadLocal threadLocal = new InheritableThreadLocal();

再执行,就能看到:

Thread-0:0
Thread-1:1
Thread-2:2
Thread-3:3
Thread-4:4
  1. 变量的传递是发生在线程创建的时候,如果不是新建线程,而是用了线程池里的线程,就不灵了
  2. 变量的赋值就是从主线程的map复制到子线程,它们的value是同一个对象,如果这个对象本身不是线程安全的,那么就会有线程安全问题

4.2 InheritableThreadLocal可继承的源码分析

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

由上代码可知,InheritableThreadLocal继承了ThreadLocal,并重写了三个方法。从createMap可以看出,当第一次调用set方法时,创建的是当前线程的inheritableThreadLocals而不是threadLocals。由重写的getMap可知,当调用get方法获取当前线程的map时,获取的是inheritableThreadLocals而不是threadLocals

那么重写的childValue()方法是何时调用的?以及如何让子线程可以访问父线程的本地变量。这要从创建Thread的代码说起,打开Thread类的构造函数,代码如下:

public class Thread implements Runnable {	
	Thread(Runnable target, AccessControlContext acc) {
        init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
    }
	private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        ...
        //获取当前线程(代码a)
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        ...
        //如果父线程的inheritThreadLocals变量不为null(代码b)
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            //设置子线程中的inheritThreadLocals变量(代码c)
            this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
}

如上代码在创建线程时,在构造函数中会调用init方法,代码a获取了当前线程(这里指的main函数所在的线程,也就是父线程),然后代码b判断main函数所在线程里面的 inheritableThreadLocals属性是否为null,前面我们提到了 InheritableThreadLocal类的getset方法操作的是 inheritableThreadLocals,所以这里的inheritableThreadLocal变量不为null,因此会执行代码c。下面看一下createInheritabledMap的代码;

	static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

	private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        //代码d
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }	

ThreadLocalMap的构造函数内部把父线程的inheritableThreadLocals成员变量的值复制到新的ThreadLocalMap对象中,其中代码d调用了InheritableThreadLocal类重写的代码;

InheritableThreadLocal通过重写方法让本地变量保存到了具体线程的inheritableThreadLocals变量里面,那么线程在通过InheritableThreadLocal类实例的set或者get方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。当父线程创建子线程时,构造函数会把父线程中的inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals变量里面;

5. ThreadLocalRandom 分析

ThreadLocalRandom 类是 JDK7在 JUC包下新增的随机数生成器,它弥补了 Random 多线程下的缺陷 。

5.1 Random存在的弊端

public class RandomDemo {
    public static void main(String[] args) {
        //1. 创建一个默认种子的随机数生成器
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            //2. 输出10个0~5(包含0不包含5)之间的随机数
            System.out.println(random.nextInt(5));
        }
    }
    
    public int nextInt(int bound) {
        //3. 参数检查
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
		//4. 根据老的种子生成新的种子
        int r = next(31);
        //5. 根据新的种子生成随机数
        int m = bound - 1;
        if ((bound & m) == 0)  // i.e., bound is a power of 2
            r = (int)((bound * (long)r) >> 31);
        else {
            for (int u = r;
                 u - (r = u % bound) + m < 0;
                 u = next(31))
                ;
        }
        return r;
    }
    
    protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            //获取当前原子变量种子的值
            oldseed = seed.get();
            //根据当前种子值计算新的种子
            nextseed = (oldseed * multiplier + addend) & mask;
            //CAS操作替换种子
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
    }
}
//Random类新的随机数生成有两个步骤:
1. 根据老的种子生成新的种子
2. 根据新的种子来计算新的随机数

在单线程情况下每次调用 nextlnt 都是根据老的种子计算出新的种子,这是可以保证随机数产生的随机性的。

但是 在多线程下多个线程可能都拿同一个老的种子去执行计算新的种子,这会导致多个线程产生的新种子一样。所以Random中next()实现自旋操作(只能有一个线程能成功)避免多线程情况下产生了同样种子的问题;

next() 方法中,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这会降低并发性能,所以ThreadLocalRandom应运而生;

5.2 ThreadLocalRandom

public class ThreadLocalRandomDemo {

    public static void main(String[] args) {
        ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
        for (int i = 0; i < 10; i++) {
            System.out.println(threadLocalRandom.nextInt(5));
        }
    }
}

ThreadLocal中我们介绍了,ThreadLocal通过让每一个线程复制一份变量,使得在每个线程对变量进行操作时实际是操作自己本地内存里面的副本,从而避免了对共享变量进行同步。ThreadLocalRandom的实现也是这个原理。

Random的缺点是多个线程使用同一个原子性种子变量,导致竞争。ThreadLocalRandom每个线程都维护一个种子变量;


6. 引用

《Java并发编程之美》_翟陆续

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王叮咚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值