跟我学Java多线程——ThreadLocal

ThreadLocal是什么

ThreadLocal这个词如果直接翻译就是“本地线程”,可是如果真的按“本地线程”来理解,那就确实大错特错了,ThreadLocal它并不是一个Thread,它跟Thread确实有关系,是用来维护Thread的有关变量的,把它命名为ThreadLocalVariable可能更容易让人理解,在多线程中ThreadLocal为变量在每个线程中都创建了一个跟特定线程有关的变量的副本,这样就可以使每个线程在运行中只可以使用与自己线程有关的特定的副本变量,而不会影响其它线程的副本变量,保证了线程间变量的隔离性。

对于线程来说,当多个线程都用到变量时,通过ThreadLocal使每个线程都有一个本地的独属于自己的变量,这也是类名中“Local”所要表达的意思。

ThreadLocal例子

ThreadLocal内部其实是一个map集合,key是各自的线程,value是我们要放入的对象。我们先通过一个ThreadLocal的简单demo先来理解一下ThreadLocal。

先来看MyThreadScopeData实体类,是对对象类型的数据封装,让外界不可直接操作ThreadLocal变量,我们通过单例模式得到这个实体类,这样的话让这个类针对不同线程分别创建一个独立的实例对象,然后将这个实例对象作为变量放到ThreadLocal中。

package com.tgb.threadlocal;

/**
 * 与ThreadLocal有关的实体对象
 * @author kang
 *
 */
public class MyThreadScopeData {

	//声明一个ThreacLoacl
	private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();

	//通过单例模式来获取对象实例
	private MyThreadScopeData(){}
	public static MyThreadScopeData getThreadInstance(){
		MyThreadScopeData instance=map.get();
		if (instance ==null) {
			instance = new MyThreadScopeData();
			map.set(instance);
		}
		return instance;
	}

	private String name;
	private int age;

	public static ThreadLocal<MyThreadScopeData> getMap() {
		return map;
	}
	public static void setMap(ThreadLocal<MyThreadScopeData> map) {
		MyThreadScopeData.map = map;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}

}

接下来是客户端调用类,我们在客户端代码中通过制造了两个线程,在每个线程中都生成一个随机数,然后把这个随机数放入到两个不同的threadlocal对象中,然后将数据从threadlocal中取出,打印出线程名称和取出来的数据;除了client客户端还包含了两个内部类,是用来从threadlocal对象中取出线程名称和数据并打印的

package com.tgb.threadlocal;

import java.util.Random;

/**
 * 测试类关于ThreadLocal
 * @author kang
 *
 */
public class ThreadLocalTest {

	private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();

	public static void main(String[] args) {
		ThreadLocal<String> ss = new ThreadLocal<String>();
		//制造两个线程
		for (int i = 0; i < 2; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					//生成一个随机数并打印
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName()
							+ " has put data :" + data);

					//将随机数放入两个不同的ThreadLocal中
					x.set(data);
					MyThreadScopeData.getThreadInstance().setName("name" + data);
					MyThreadScopeData.getThreadInstance().setAge(data);

					//从ThreadLocal中取出数据并打印
					new A().get();
					new B().get();
					System.out.println("#########################################");
				}
			}).start();

		}
	}

	//内部类A,从两个ThreadLocal对象中取出数据,并打印
	static class A {
		public void get() {
			//从value为int类型的ThreadLocal中取出数据,并打印
			int data = x.get();
			System.out.println("A from " + Thread.currentThread().getName()
					+ " get int :" + data);

			//从ThreadLocal实体对象中取出线程中放入的数据
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out
					.println("A from " + Thread.currentThread().getName()
							+ " 实体对象数据: " + myData.getName() + ","
							+ myData.getAge());
		}
	}

	//内部类B,从两个ThreadLocal对象中取出数据,并打印
	static class B {
		public void get() {
			//从value为int类型的ThreadLocal中取出数据,并打印
			int data = x.get();
			System.out.println("B from " + Thread.currentThread().getName()
					+ " get int :" + data);

			//从ThreadLocal实体对象中取出线程中放入的数据
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out
					.println("B from " + Thread.currentThread().getName()
							+ " 实体对象数据: " + myData.getName() + ","
							+ myData.getAge());
		}
	}
}

我们看一下执行的结果:

Thread-1 has putdata :-458782705

A from Thread-1 getint :-458782705

A from Thread-1实体对象数据: name-458782705,-458782705

B from Thread-1 getint :-458782705

B from Thread-1实体对象数据: name-458782705,-458782705

#########################################

Thread-0 has putdata :1881149941

A from Thread-0 getint :1881149941

A from Thread-0实体对象数据: name1881149941,1881149941

B from Thread-0 getint :1881149941

B from Thread-0实体对象数据: name1881149941,1881149941

#########################################

通过执行结果我们可以看出,在两个线程的执行过程中生成的随机数是不一样的,通过将数据放入ThreadLocal中并取出打印,我们发现每个线程中的数据是一致保持一致的,这也就证明了,ThreadLocal在同一线程中实现了线程内的数据共享,不同线程间我们实现了数据的隔离性。

ThreadLocal源码

我们现在来看下ThreadLocal背后的代码是怎样实现的。

先来看下ThreadLocal的常用方法:

public T get() { }
public void set(T value) { }
public void remove() { }

get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本

首先我们先来看下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();
    }

首先通过currentThread()获取当前运行线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap,如果map不为空就通过当前的ThreadLocal取出map中存取的value,如果为空就调用setInitialValue()方法创建ThreadLocalMap并经value进行返回。

我们继续对上面的get()方法进行详细的分析,我们接下来看下getMap(t)方法,

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

意思就是利用currentThread()获取当前运行线程t,然后得到t的成员变量threadLocals,threadLocals又是什么,我们接着往下看,

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

threadLocals是ThreadLocal类的一个内部类ThreadLocalMap,我们接着看内部类ThreadLocalMap的实现,

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

可以看到ThreadLocalMap的内部类Entry继承了WeakReference,并且使用ThreadLocal作为键值,利用ThreadLocal的变量作为value,代码跟到这里我的目的就达到了,所以这里ThreadLocalMap本质就是利用Entry构造了一个key、value对,其实关于ThreadLocal的核心代码都在这个类中,感兴趣的同学可以自己接着往下看。

我们get()方法里还有setInitialValue(),是当map为空时返回value用的,我们看下它的代码:

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

在这个方法里通过initialValue()获取value,获取当前线程,如果map不为空就对map进行赋值要注意key是当前的ThreadLocal,为空就进行创建map与直接赋值不一样key为当前Thread,同一个map为什么不一样,这里就卖个关子不再介绍了,大家在往下跟层代码看看就明白了。

我们在看setInitialValue()中的方法initialValue()如何来获取value的

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

通过ThreadLocal的内部类SuppliedThreadLocal的initialValue()方法得到泛型,并进行返回。

到这里我们就将get()方法如何为ThreadLocal的每个线程创建变量的副本的详细的介绍完了,跟踪完了以后发现确实挺简单,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

ThreadLocal与Synchronized区别

ThreadLocal和Synchonized都用于解决多线程并发访问他们两者的区别:synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问,而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享,而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。一句话来说,Synchronized是为了让多线程进行数据共享,而ThreadLocal为了让多线程进行数据隔离。

hibernate中一个典型的ThreadLocal应用,

private static final ThreadLocal threadSession = new ThreadLocal();

    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }

这段代码就是线程多实例(每个线程对应一个实例)的对象的访问,并且每个线程都有自己私有变量session。

总结

本篇文章讲解了ThreadLocal是什么,通过一个简单的demo来说明了ThreadLocal在同一线程中实现了线程内的数据共享,不同线程间我们实现了数据的隔离性,接下来通过一步步的去读ThreadLocal的get()方法的源码来详细的讲解了ThreadLocal本质,最后我们将ThreadLocal于Synchronized进行了比较。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-08 21:52:01

跟我学Java多线程——ThreadLocal的相关文章

Java多线程——ThreadLocal类

一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为ThreadLocalVar更加合适.线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突. 从线程的角度看,每个线程都保持一个对其线程局

跟我学Java多线程——线程池与堵塞队列

前言 上一篇文章中我们将ThreadPoolExecutor进行了深入的学习和介绍,实际上我们在项目中应用的时候非常少有直接应用ThreadPoolExecutor来创建线程池的.在jdk的api中有这么一句话"可是,强烈建议程序猿使用较为方便的 Executors 工厂方法Executors.newCachedThreadPool()(无界线程池,能够进行自己主动线程回收).Executors.newFixedThreadPool(int)(固定大小线程池)和Executors.newSing

跟我学Java多线程——线程池与阻塞队列

前言 上一篇文章中我们将ThreadPoolExecutor进行了深入的学习和介绍,实际上我们在项目中应用的时候很少有直接应用ThreadPoolExecutor来创建线程池的,在jdk的api中有这么一句话"但是,强烈建议程序员使用较为方便的 Executors 工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收).Executors.newFixedThreadPool(int)(固定大小线程池)和Executors.newSingleT

跟我学Java多线程——ThreadPoolExecutor(线程池)

什么是线程池 多线程开发中,由于线程数量多,并且每个线程执行一段时间就结束,所以要频繁的创建线程,但是这样频繁的创建线程会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间.在这种情况下,人们就想要一种可以线程执行完后不用销毁,同时该线程还可以去执行其他任务,在这样的情况下线程池就出现了. 线程池就是线程的池子,任务提交到线程池后,就从线程池中取出一个空闲的线程为之服务,服务完后不销毁该线程,而是将该线程还回到线程池中. 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,

java 多线程-ThreadLocal

ThreadLocal:每个线程自身的存储本地.局部区域,类似于容器,每个线程都会在其中有一定存储空间常用的方法get/set/initialValue官方建议为private static每个线程存储自己的数据,更改不会影响其他线程ThreadLocal 子类InheritableThreadLocal:继承上下文环境的数据 public class my { //Integer 初始值为null //private static ThreadLocal<Integer> threadloc

Java多线程-ThreadLocal和InheritableThreadLocal的使用

ThreadLocal类 变量值的共享可以通过使用public static变量的形式.如果想每个线程都有自己的共享变量,可以通过JDK提供的ThreadLocal来实现. 类 ThreadLocal 主要解决的就是每个线程绑定自己的值,可以将 ThreadLocal 类比喻成全局存放数据的盒子,盒子中可以存放每个线程的私有变量. 示例: public class Test { public static void main(String[] args) { try { for (int i =

java 多线程-ThreadLocal图

原文地址:https://blog.51cto.com/14437184/2430518

跟着实例学习java多线程-2

上一篇文章我们通过一个实例来说明了并发编程为什么要做同步处理,下面我们再来巩固一下. 对象如果拥有可变状态的变量,并且被多线程访问,那么这个时候我们要对可变状态变量的状态改变做原子操作处理. 锁机制是保证这样的操作的一个有效的方法,它可以保证变量的状态在被更新时是在一个原子操作中进行的. java提供了一种内置锁机制来支持原子性:同步代码块(Synchronized Block). 同步代码块包括两个部分:一个是作为锁的对象引用,一个是作为由这个锁保护的代码块. 让我们在来回忆上一篇文章最后的问

Rhythmk 一步一步学 JAVA (21) JAVA 多线程

1.JAVA多线程简单示例 1.1 .Thread  集成接口 Runnable 1.2 .线程状态,可以通过  Thread.getState()获取线程状态: New (新创建) Runnable (可以运行) Blocked  (被阻塞) Waiting  (等待) Timed waiting (计时等待) Terminated  (被终止) ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27