线程本地存储—ThreadLocal,map实现

  1. Entry和Map的关系???

ThreadLocal简介

  public static修饰的变量可以让所有线程共享,但是如果让每个线程有自己独享的数据对象,则使用java.lang.ThreadLocal类。他可以将线程和数据对象绑定在ThreadLocal的静态内部类ThreadLocalMap中,以实现线程独享数据对象。
  《java并发编程实战》中提到两种情况下可用ThreadLocal将数据集与线程绑定:

  1. 防止对可变单实例变量或者全局变量进行共享;
  2. 当某个频繁的操作需要一个临时对象,同时有希望避免在每次执行时都重新分配该对象;
1.使用示例
package me.demo;

public class Accessor implements Runnable {
    private final int id;

    public Accessor(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        while(!Thread.currentThread().isInterrupted()){//不会清空"中断状态": return isInterrupted(false)
            ThreadLocalVariableholder.increment();//TODO:1.为当前线程设置值;2.被操作的变量是static的
            System.out.println(this);
            Thread.yield();
        }
    }

    @Override
    public String toString() {
        return "#"+id+": "+ThreadLocalVariableholder.get();
    }
}
package me.demo;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @date 2017/12/12
 */
public class ThreadLocalVariableholder {
    //静态变量,与类绑定
    private static ThreadLocal value=new ThreadLocal(){
        private Random random=new Random();

        @Override
        protected synchronized Integer initialValue() {//父类方法是没有synchronized关键字的
            return random.nextInt();
        }
    };

    //使用get()/set()实现数据对象+1
    public static void increment(){
        value.set(value.get()+1);
    }
    //返回数据对象的一个副本
    public static int get(){
        return value.get();
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService= Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executorService.execute(new Accessor(i));
        }
        TimeUnit.SECONDS.sleep(1);
        executorService.shutdown();
    }

}

  即便只有一个ThreadLocalVariableHolder对象,每个线程也仍有自己的数据对象。ThreadLocal保证不会出现竞态条件。

1.ThreadLocal类源码分析

  1)创建对象;

    public ThreadLocal() {}
    //由此可知我们可以使用 ThreadLocal t=new ThreadLocal()为其创建一个对象

  2)为当前线程设置值;

/**
 * 调用此方法的当前线程会与其数据集绑定;
 * ThreadLocal创建对象时可以重写protected T initialValue()来初始化数据集,
 * 而不用专门调用set(T value)方法
 */
    public void set(T value) {
        Thread t = Thread.currentThread();//返回当前线程的一个引用
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);//将当前线程与数据对象绑定
        else
            createMap(t, value);
    }

  3)获取当先线程绑定的数据集

    /**
     * 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);//根据当前对象获取其对应的value,即数据对象。
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果当前线程没有绑定数据对象,则返回初始化值。
        return setInitialValue();
    }

  4)为指定线程和数据对象创建映射,即将制定对象绑定到指定数据对象;

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

   由上行代码可猜测:程序会为线程对象绑定数据集,而且初始化线程对象的属性threadLocals ,属性在ThreadLocal中如下所示:

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

之所以所示猜测,是因为代码比自己想象的要复杂,其中createMap调用的构造函数代码如下:

        /**
         * 构造一个包含键值对的map。ThreadLocalMaps的对象构造是懒汉式的,所以
         * 当我们有需要时才创建他。
         * 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);
        }

你可能感兴趣的:(java,并发,多线程)