线程的共享和协作

目录

  1. 并发编程的一些定义和概念
    1.1、并行和并发的区别
    1.2、多线程的安全注意事项
  2. 线程的使用
    2.1、线程的启动和中止
    2.2、run()和start()的区别
    2.3、其他线程方法
    2.4、synchronized内置锁和volatile关键字
  3. ThreadLocal
    3.1、ThreadLocal的使用
    3.2、ThreadLocal的与synchronized的比较
    3.3、ThreadLocal的实现解析
    3.4、ThreadLocal不规范使用导致的内存泄漏分析
    3.5、ThreadLocal错误使用导致的线程不安全问题
  4. 线程的等待和通知机制
  5. 末尾

一些基础知识我会略过,不了解的可以看我这篇文章javaSE—多线程基础


一、并发编程的一些定义和概念

1.1、并行和并发的区别

并行:指同时运行的多个线程,宏观和微观都是同时运行
并发: 指单个CPU核心下单位时间内运行了多少个线程,宏观并行,微观串行

并行纯靠硬件技术,并发软件技术也可以出力

并发概念核心词: 单位时间内、单核
并发跟速度一样,脱离了时间限定,也就无所谓并发这概念。
核也可以并发,上面只是为了强调并发是为了将每个CPU的运行效率最大化

1.2、多线程的安全注意事项

  1. 多线程的安全性:共享资源的数据一致性
  2. 线程死锁
  3. 线程太多了会将服务器资源耗尽形成死机当机。
    每启动一个线程,OS将会分配 1M左右 的 栈空间 给它(还不包括代码里其他变量对象的空间)

二、线程的使用

2.1、线程的启动和中止

2.1.1 新线程的创建方式只有两种:

  1. Thread方式
  2. Runnable方式(包括了Callable)
    //重看callable 和 源码

JDK英文文档——Thread类
在这里插入图片描述

2.1.2 线程的启动:start()

2.1.3 (重点)线程的中止:interrupt()、stop()
推荐使用interrupt()

线程.stop():将线程强制打断,不管该线程是否执行完或者释放了资源没。(不推荐使用)

线程.interrupt(): 将线程的中断标志位设为true,而不是强制打断(是线程协作式的典型)
interrupt()的步骤:

  1. 在B线程中执行A.interrupt();
  2. A线程中获取中断位变化:isInterrupted() 或者 Thread.interrupted();

B线程执行A线程.interrupt(),仅仅是通知A线程请它中断,A线程是否要中断,完全由A线程自己决定

isInterrupted() 与 Thread.interrupted()的区别:

  • isInterrupted()是Thread类的普通方法,仅仅返回当前线程的中断标志位的值
  • Thread.interrupted()是Thread类的 静态 方法,在返回当前线程的中断标志位的值后,还会将中断位设为false
  • 在源码中isInterrupted() 和 Thread.interrupted() 的区别只有调用isInterrupted(boolean)的参数不同而已,isInterrupted()是传进去false,Thread.interrupted()是传进去true

isInterrupted()源码:
在这里插入图片描述
Thread.interrupted()源码:
在这里插入图片描述
在这里插入图片描述

public class EndThread {
	public static void main(String[] args) throws InterruptedException {
		InterruptThread t1 = new InterruptThread("中断一号");
		t1.start();
		t1.sleep(50);
		t1.interrupt();
//		t1.stop();
	}
}

class InterruptThread extends Thread{
	public InterruptThread(String name) {
		super(name);
	}
	public void run() {
//		//完全不理会interrupt标志位的变化
//		while(true) {
//			System.out.println("我完全不理会中断位变化,不中断");
//			try { sleep(1000); } catch (InterruptedException e) { }
//		}
		
		//理会interrupt标志位的变化
		while(!isInterrupted()) {//返回当前线程的中断标志位的值,true中断,false不中断
//		while(!Thread.interrupted()) {//返回线程的中断标志位的值,而且会修改中断位为false
			System.out.println(Thread.currentThread().getName()
					+"还没被中断,中断标志位:"+isInterrupted());
		}
		//如果使用stop(),while循环后的代码都不会执行
		System.out.println("执行中断后的标志位:"+isInterrupted());
	}
}

线程可以完全不理会interrupt标志位的变化:
在这里插入图片描述
isInterrupted():
在这里插入图片描述
Thread.interrupted():
在这里插入图片描述
总结:

  • A线程.interrupt():将A线程中断标志位设为true
  • isInterrupted():返回当前线程的中断标志位的值
  • Thread.interrupted():返回当前线程的中断标志位的值,并将当前线程的中断位设为false
  • A线程.interrupted():返回当前线程的中断标志位的值,并将A线程的中断位设为false( interrupted()是静态方法,其实三、四是一样的)

2.2、run()和start()的区别

start():启动新线程
run():不启动新线程,仅仅调用该线程的Runnable成员对象的run方法

Thread.start()源码:start0()——>这里启动新线程

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();//这里启动新线程
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

Thread.run()源码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3、其他线程方法

JDK中文文档——Thread类

1. currentThread()
2. sleep():释放CPU资源,不释放锁
3. yield():释放CPU资源,不释放锁
4. wait():释放CPU资源,释放锁,只有wait()可以释放锁
5. join():释放CPU资源,释放锁,底层调用wait(),所以才能释放锁
6. stop()suspend()resume()
7. run()、start()
8. get:-Id()、-Name()、-Priority()、-State()
9. set:-Name()、-Priority()、-Daemon()
10. is:-Daemon()、-Alive()
11. 中断:interrupt()
12. interrupted() (static方法)、isInterrupted()

2.4、synchronized内置锁和volatile关键字

1、synchronized
以前写的关于synchronized内置锁的文章
挺详细的,就不重复写了。

2、volatile
最低级别的同步锁,仅仅实现对共享资源的实时读取的效果,即可见性

volatile两个作用:

  1. 可见性:保证所有线程读取该共享变量时,都是读取到最新值
  2. 禁止指令重排序:保证在执行读或写volatile变量时,在该变量前所有命令都已经执行,在该变量后的所有命令都未执行。

程序猿的内心独白——这是一篇关于volatile的文章,比较详细,(后面我在自己整理一下关于volatile的文章,感觉不是很清晰)

3、整理一下:

  1. volatile是通过在volatile原子操作前 后 加Lock即内存屏障来实现上面两个功能的
  2. volatile只能保证实时读取,不能保证线程安全,不能保证写操作的正确性,还有不能解决多CPU的 并行写 问题

整理这里的两个细节:

  1. volatile原子操作:
    这是volatile的原子操作(读)
	volatile int i = 0;
	public static void main(String[] args)  {
		System.out.println(i);
	}

这不是volatile的原子操作(读、写),多线程操作时就会有可能出问题

	volatile int i = 0;
	public static void main(String[] args)  {
		System.out.println(++i);
	}
  1. volatile不能保证并行写的问题,但可以保证并行读的问题,注意是并行,不只是并发
    在单cpu操作volatile变量时,JVM不会给volatile语句加特殊的LOCk,就算多线程并发也是单cpu,但是当多CPU并行操作该volatile变量时,JVM会给变量加特殊的LOCK,以保证volatile的可见性的正确性,所以,volatile可以保证并行读的正确性
  2. volatile能不能保证原子性?
    只能保证一定程度上的原子性,即实时读取,不能保证对该变量的所有操作都是原子性。
  3. volatile能不能保证有序性?
    只能保证一定程度上的有序性,即volatile前面都执行了,后面都没执行,但不能保证前面或者后面的代码的有序性。

林夕-长——这是关于volatile的一些细节问题总结

4、使用 volatile的应用场景:

  1. 对变量的写操作不依赖于当前值。(因为它不能保证并发写的正确性)
  2. 该变量没有包含在具有其他变量的不变式中(只保证volatile变量的读原子操作)

个人觉得禁止指令重排序是实现可见性的基础

三、ThreadLocal

ThreadLocal的中文文档

ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储.

但其实 ,ThreadLocal 更准确的说是个 线程数据副本调用器,而不是线程本地变量/存储

详细解释留到下面的 3.3、实现解析 再说,毕竟先用用再解释原理会多点实感

3.1、ThreadLocal的使用

ThreadLocal只有四个方法,使用起来较简单,分别是:

  1. get():返回该线程数据副本值,原文:返回此线程局部变量的当前线程副本中的值。
  2. set(T value): 获取该线程数据副本值,原文:将此线程局部变量的当前线程副本中的值设置为指定值。
  3. remove():移除该线程数据副本值,原文:移除此线程局部变量当前线程的值。
  4. initialValue():返回该线程数据副本值,原文:返回此线程局部变量的当前线程的“初始值”。

ThreadLocal的两种创建、get()、set()、remove():
(ThreadLocal的创建一般都是 + private static 的成员变量)

package com.Thread.ThreadLocal;

/**
 *类说明:演示ThreadLocal的使用
 */
public class UseThreadLocal {
	//第一种写法
	private static ThreadLocal<Integer> tLocal = new ThreadLocal<>();
//	private static ThreadLocal<String> tLocal = new ThreadLocal<>();
	
	//第二种写法,一般都是用第二种,创建时自己设初始值
//	private static ThreadLocal<Integer> tLocal = new ThreadLocal<Integer>() {	//如果要写匿名类,new后必须加Integer实际类型,不能<>。
//		protected Integer initialValue() {
//			return 10;		//重写initialValue(),更改初始值,不改的话默认值是null
//		}
//	};
	
	public static void main(String[] args) {
		Runnable r1 = new Runnable() {
			public void run() {
//				int a = tLocal.get(); tLocal.set(++a); 
				tLocal.set((tLocal.get()+1));
				System.out.println(Thread.currentThread().getName()+": "+tLocal.get());
			}
		};
		for(int i=0;i<5;i++)
			new Thread(r1,"T-"+i).start();
//		用第一种的Integer泛型会报错,因为它默认值是null,
//		数值为null还拿去给变量赋值,就会报空指针异常
	}
}

第一种写法Integer类型结果:
在这里插入图片描述

第一种写法String类型结果:
在这里插入图片描述
第二种写法Integer类型结果:
在这里插入图片描述

initialValue():

  • 供ThreadLocal需要初始化数据时使用,创建 和 remove()后对象为null时。
  • 使用:不可调用,创建时重写,起到更改默认值的作用。(像上面的第二种写法)

3.2、ThreadLocal的与synchronized的比较

ThreadLocal 和 synchronized 都用于解决多线程并发访问的问题,本质的区别在于:多线程并发访问操作的变化是否会相互影响。

  • ThreadLocal:为每一个线程创建各自的线程数据副本,各自操作各自的数据,互不影响。(每个人都有自己的游戏账号,各玩各的)
  • synchronized:所有线程都是用同一个数据,每个线程的操作都会影响到其他线程的操作。(所有人用同一个游戏账号,大家一起玩)

ThreadLocal利用静态内部类ThreadLocalMap提供线程副本变量。
synchronized通过锁来实现多线程并发访问同一个共享变量时的数据安全性。

3.3、ThreadLocal的实现解析

1.ThreadLocal 本身:

为什么说 :ThreadLocal 更准确的说是个 线程数据副本调用器,而不是线程本地变量/存储?

线程本地变量/存储,实际上是ThreadLocal的静态内部类ThreadLocalMap。
在这里插入图片描述

你可以把ThreadLocal看成单纯的这样的一个类:集合了 多个使用 一张key—value表 的方法 的类。emmm,就一些方法而已。

ThreadLocal最特殊的地方在于:它本身不保存任何数据,是的,上面的key-value表并不是ThreadLocal对象的成员变量,value,key它都不会保存,所以才说ThreadLocal是一个 线程数据副本调用器,而不是线程副本类

2.ThreadLocal 使用的key—value表:以ThreadLocal自身作为key

ThreadLocal 的key—value表:ThreadLocalMap的成员变量Entry(ThreadLocal,Object)数组

ThreadLocalMap是ThreadLocal的静态内部类,Entry是ThreadLocalMap的静态内部类。

所以实际上是存储在ThreadLocalMap类中的Entry对象数组中

Entry:

  • key:ThreadLocal类型,当前使用的ThreadLocal对象
  • value:Object,你所要存储的数据对象(但是ThreadLocal用了泛型,而泛型参数都是引用对象,所以别int,要用Integer)
    在这里插入图片描述

在这里插入图片描述

问题1:为什么不说ThreadLocal类是线程本地变量,或者Entry类才是线程本地变量?
实际存储是在ThreadLocalMap类中,不是ThreadLoca类,也不是Entry类。
存储地方是在ThreadLocalMap中的成员变量Entry数组中,而ThreadLocal中并没有ThreadLocalMap成员变量,Entry更是个简单的类而已,就一个骨架。

问题2:ThreadLocalMap又是在哪里呢?我们直接使用的只是ThreadLocal,又不是ThreadLocalMap。
这就是我为什么说ThreadLocal实际上是个 线程数据副本调用器 的原因
Thread线程类有一个成员变量ThreadLocalMap,JDK注明该成员变量就是用来给ThreadLocal类使用的。
我们调用ThreadLocal类实际上就是在通过ThreadLocal的方法去使用对应线程的ThreadLocalMap成员变量,所以ThreadLocal只是个调用器,而不是线程本地副本,线程本地副本实际上是线程自身的ThreadLocalMap成员变量。

在这里插入图片描述
3、调用流程

调用流程用get()说一遍吧,set()差不多就不写了
想看ThreadLocal类源码的点这里

get():

  1. 通过当前线程对象,获取当前线程的线程本地副本,直白点就是获取自身线程的线程本地副本在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 判断线程本地副本ThreadLocalMap是否为null,是null,说该线程没有任何本地副本,自然没有我们使用的ThreadLocal的对应副本,就会重新初始化副本值,并返回初始值
    在这里插入图片描述
  3. 本地副本ThreadLocalMap不为空时,用我们使用的ThreadLocal对象作为key,查询是否有对应的线程副本存储值,有就返回,没有就会跟上一步一样,重新初始化副本值,并返回初始值
    在这里插入图片描述

3.4、ThreadLocal不规范使用导致的内存泄漏分析

内存泄漏涉及到对象引用的知识,这里简单写一下

1、对象引用:

1.1 引用种类:

  1. 强引用:必需的对象引用,只要强引用还在,垃圾回收器就不会回收被引用的实例 。( 类似Object o = new Object(); 后面的new Object()实例不会被回收,直到程序结束才会回收)
  2. 软引用:有用但不是必需的对象引用。(在系统内存不足即将发生内存溢出时才会回收,如果回收后还不足就会抛出内存溢出异常)
  3. 弱引用:指不重要的对象引用,只要发生垃圾回收,它就会被回收
  4. 虚引用:也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用
    来取得一个对象实例
    。为一个对象设置虚引用关联的唯一目的就是能在这个对象
    实例被收集器回收时收到一个系统通知。

写法:
在这里插入图片描述

1.2 建立引用
Object o = new Object();的三步骤:
      Object o ——> 在栈中生成一个对象
      new Object() ——> 在堆中生成一个对象实例
      =(赋值) ——> 在 o 与 对象实例 之间 建立引用

所以Object o = null;——>在栈中生成了一个对象,却没有生成实例和建立引用

2、ThreadLocalMap的存储方式
Entry(弱引用的ThreadLocal,强引用的value)
在这里插入图片描述
Entry的key是弱引用,那么在程序运行时如果发生垃圾回收,那么Entry的Key就会被回收(null)
这个时候问题就来了,Entry [ ]的元素Entry类型是存储两个值的,例如Entry [0] 的 key虽然被置为null了,但value没有。

那么Entry [0] 因为value不为空,它就不会被当成null,但是Entry [0]因为key为null,实际上已经是没用的垃圾,空占空间(value的大小)。

这就是ThreadLocal的内存泄漏问题的根源

解决办法;remove()——>及时清除不用的线程本地副本,用完就删

3.5、ThreadLocal错误使用导致的线程不安全问题

使用ThreadLocal存储数据到线程本地变量时,存储的数据不能是static修饰的

原因:static变量是唯一引用实例,就是使用了static后,不管你怎么写,你用的都会是同一个实例
而ThreadLocal的本质是通过ThreadLocalMap实现不同的线程本地副本,自然会出现线程不安全问题

加了static后就跟没写ThreadLocal一样,就不演示了

四、线程的等待和通知机制

wait()会释放锁

  1. 锁. wait():不限时间的等待,就一直等待
  2. 锁. wait(long a):最多等待a毫秒,时间到了会自动醒来
  3. 锁. wait(long a,int b):最多等待a毫秒 + b纳秒,时间到了会自动醒来
  4. 锁. notify():随机唤醒,少用
  5. 锁. notifyAll():全唤醒,多用




(返回 调用流程)ThreadLocal全文源码:

* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.lang;
import java.lang.ref.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
 * and remains unchanged on subsequent calls.
 * <pre>
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // Atomic integer containing the next thread ID to be assigned
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // Thread local variable containing each thread's ID
 *     private static final ThreadLocal&lt;Integer&gt; threadId =
 *         new ThreadLocal&lt;Integer&gt;() {
 *             &#64;Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // Returns the current thread's unique ID, assigning it if necessary
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * </pre>
 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {
    /**
     * ThreadLocals rely on per-thread linear-probe hash maps attached
     * to each thread (Thread.threadLocals and
     * inheritableThreadLocals).  The ThreadLocal objects act as keys,
     * searched via threadLocalHashCode.  This is a custom hash code
     * (useful only within ThreadLocalMaps) that eliminates collisions
     * in the common case where consecutively constructed ThreadLocals
     * are used by the same threads, while remaining well-behaved in
     * less common cases.
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * The next hash code to be given out. Updated atomically. Starts at
     * zero.
     */
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    /**
     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    /**
     * 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;
    }

    /**
     * Creates a thread local variable. The initial value of the variable is
     * determined by invoking the {@code get} method on the {@code Supplier}.
     *
     * @param <S> the type of the thread local's value
     * @param supplier the supplier to be used to determine the initial value
     * @return a new thread local variable
     * @throws NullPointerException if the specified supplier is null
     * @since 1.8
     */
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

    /**
     * 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) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * 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();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    /**
     * 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
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

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

    /**
     * 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 ThreadLocalMap(this, firstValue);
    }

    /**
     * Factory method to create map of inherited thread locals.
     * Designed to be called only from Thread constructor.
     *
     * @param  parentMap the map associated with parent thread
     * @return a map containing the parent's inheritable bindings
     */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

    /**
     * Method childValue is visibly defined in subclass
     * InheritableThreadLocal, but is internally defined here for the
     * sake of providing createInheritedMap factory method without
     * needing to subclass the map class in InheritableThreadLocal.
     * This technique is preferable to the alternative of embedding
     * instanceof tests in methods.
     */
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

    /**
     * An extension of ThreadLocal that obtains its initial value from
     * the specified {@code Supplier}.
     */
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        private final Supplier<? extends T> supplier;

        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    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;

        /**
         * 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

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         *
         * @param parentMap the map associated with parent thread.
         */
        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) {
                        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++;
                    }
                }
            }
        }

        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        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;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

        /**
         * Replace a stale entry encountered during a set operation
         * with an entry for the specified key.  The value passed in
         * the value parameter is stored in the entry, whether or not
         * an entry already exists for the specified key.
         *
         * As a side effect, this method expunges all stale entries in the
         * "run" containing the stale entry.  (A run is a sequence of entries
         * between two null slots.)
         *
         * @param  key the key
         * @param  value the value to be associated with key
         * @param  staleSlot index of the first stale entry encountered while
         *         searching for key.
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // Find either the key or trailing null slot of run, whichever
            // occurs first
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

        /**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

        /**
         * Heuristically scan some cells looking for stale entries.
         * This is invoked when either a new element is added, or
         * another stale one has been expunged. It performs a
         * logarithmic number of scans, as a balance between no
         * scanning (fast but retains garbage) and a number of scans
         * proportional to number of elements, that would find all
         * garbage but would cause some insertions to take O(n) time.
         *
         * @param i a position known NOT to hold a stale entry. The
         * scan starts at the element after i.
         *
         * @param n scan control: {@code log2(n)} cells are scanned,
         * unless a stale entry is found, in which case
         * {@code log2(table.length)-1} additional cells are scanned.
         * When called from insertions, this parameter is the number
         * of elements, but when from replaceStaleEntry, it is the
         * table length. (Note: all this could be changed to be either
         * more or less aggressive by weighting n instead of just
         * using straight log n. But this version is simple, fast, and
         * seems to work well.)
         *
         * @return true if any stale entries have been removed.
         */
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

        /**
         * 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();
        }

        /**
         * Double the capacity of the table.
         */
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

        /**
         * Expunge all stale entries in the table.
         */
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }
}

回到顶部

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

White–Night

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

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

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

打赏作者

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

抵扣说明:

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

余额充值