1.简述ThreadLocal

  ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。ThreadLocal是一个线程级别的局部变量,下面是线程局部变量(ThreadLocal variables)的关键点:

  A、当使用ThreadLocal维护变量时,若多个线程访问ThreadLocal实例,ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。

  B、从线程的角度看,目标变量就像是线程的本地变量,这也是类名中Local所要表达的意思。

2.细看ThreadLocal

ThreadLocal<T>类很简单,只有四个方法:

(1)void set(T value),该方法用来设置当前线程中变量的副本

(2)public T get(),该方法是用来获取ThreadLocal在当前线程中保存的变量副本

(3)public void remove(),该方法用来移除当前线程中变量的副本,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束以后,对应线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

(4)protected T initialValue(),该方法是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,ThreadLocal中的缺省实现直接返回一个null。

3.ThreadLocal示例

简单的使用方法如下:

package com.test;

public class ThreadMain {
// ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
public Integer initialValue() {
return 0;
}
}; // ②获取下一个序列值
public int getNextNum() {
seqNum.set(seqNum.get() + 1);
return seqNum.get();
} public static void main(String[] args) {
ThreadMain sn = new ThreadMain();
// ③ 3个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
} private static class TestClient extends Thread {
private ThreadMain sn; public TestClient(ThreadMain sn) {
this.sn = sn;
} public void run() {
for (int i = 0; i < 3; i++) {
// ④每个线程打出3个序列值
System.out.println("thread[" + Thread.currentThread().getName()
+ "] --> sn[" + sn.getNextNum() + "]");
}
}
} }

结果如下:

thread[Thread-0] --> sn[1]
thread[Thread-2] --> sn[1]
thread[Thread-1] --> sn[1]
thread[Thread-2] --> sn[2]
thread[Thread-0] --> sn[2]
thread[Thread-2] --> sn[3]
thread[Thread-1] --> sn[2]
thread[Thread-1] --> sn[3]
thread[Thread-0] --> sn[3]

另一个案例

package com.csu.thread;

class GlobalVarManager {
private static ThreadLocal<String> globalVars = new ThreadLocal<String>(){
protected String initialValue() {
return "hello";
}
}; public static ThreadLocal<String> getglobalVars() {
return globalVars;
}
} class ThreadRun implements Runnable { private ThreadLocal<String> t;
private String str; ThreadRun(ThreadLocal<String> temp, String s) {
this.t = temp;
this.str = s;
} @Override
public void run() {
System.out.println(Thread.currentThread()+"改变前:" + t.get());
t.set(str);
System.out.println(Thread.currentThread()+"改变后:" + t.get());
}
} public class ThreadLocalTry { public static void main(String[] args) {
for (int i =1; i < 5; i++) {
new Thread(new ThreadRun(GlobalVarManager.getglobalVars(), ""+i)).start();
}
}
}

结果如下:

Thread[Thread-0,5,main]改变前:hello
Thread[Thread-1,5,main]改变前:hello
Thread[Thread-0,5,main]改变后:1
Thread[Thread-2,5,main]改变前:hello
Thread[Thread-1,5,main]改变后:2
Thread[Thread-2,5,main]改变后:3
Thread[Thread-3,5,main]改变前:hello
Thread[Thread-3,5,main]改变后:4

上述案例也可按如下方式来实现:

package com.csu.test;

class GlobalVarManager {
private static ThreadLocal<String> globalVars = new ThreadLocal<String>(){
protected String initialValue() {
return "hello";
}
}; public static String getGlobalVars() {
return globalVars.get();
} public static void setGlobalVars(String str) {
globalVars.set(str);
}
} class ThreadRun implements Runnable { private String str = null; public ThreadRun(String temp) {
str = temp;
} @Override
public void run() {
System.out.println(Thread.currentThread()+"改变前:" + GlobalVarManager.getGlobalVars());
GlobalVarManager.setGlobalVars(str);
System.out.println(Thread.currentThread()+"改变后:" + GlobalVarManager.getGlobalVars());
}
} public class ThreadLocalTest { public static void main(String[] args) {
for (int i = 1; i < 5; i++) {
new Thread(new ThreadRun("" + i)).start();
}
}
}

结果如下:

Thread[Thread-3,5,main]改变前:hello
Thread[Thread-2,5,main]改变前:hello
Thread[Thread-1,5,main]改变前:hello
Thread[Thread-0,5,main]改变前:hello
Thread[Thread-1,5,main]改变后:2
Thread[Thread-2,5,main]改变后:3
Thread[Thread-3,5,main]改变后:4
Thread[Thread-0,5,main]改变后:1

4.ThreadLocal的实现机制

此部分内容暂没有深入研究,欲了解更多内容请参考https://www.cnblogs.com/dennyzhangdd/p/7978455.html

(1)get()方法源码如下:

(2)set()方法源码如下:

(3)remove()方法源码如下:

(4)上述几个函数涉及到如下两个函数

从前述源码可以看出,ThreadLocal的get、set、remove方法都是操作当前线程,而从Thread的源码可以看出该类有一个ThreadLocal.ThreadLocalMap类型的变量threadLocals,该变量在初次调用ThreadLocal的set()方法时通过createMap()方法初始化

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

此处重点关注一下ThreadLocalMap中的几个成员变量及方法

(1)private Entry[] table;

table是一个Entry类型的数组,该变量在ThreadLocalMap的构造函数中初始化

Entry是ThreadLocalMap的一个内部类

(2)set()方法

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

(3)getEntry()方法

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

(4)remove()方法

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

6.总结

ThreadLocal一般都是声明在静态变量中,如果不断地创建ThreadLocal而没有调用其remove方法,将导致内存泄露,特别是在高并发的Web容器当中。

ThreadLocal在处理线程的局部变量时比synchronized同步机制解决线程安全问题更简单,更方便,且程序拥有更高的并发性。

java并发:线程同步机制之ThreadLocal的更多相关文章

  1. Java 并发 线程同步

    Java 并发 线程同步 @author ixenos 同步 1.异步线程本身包含了执行时需要的数据和方法,不需要外部提供的资源和方法,在执行时也不关心与其并发执行的其他线程的状态和行为 2.然而,大 ...

  2. java synchronized 线程同步机制详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...

  3. Java并发——线程同步Volatile与Synchronized详解

    0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...

  4. 【总结】Java线程同步机制深刻阐述

    原文:http://hxraid.iteye.com/blog/667437 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread ...

  5. ThreadLocal和线程同步机制对比

    共同点: ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题. 区别: 在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量. 这时该变量是多个线程共享的,使用同 ...

  6. Java多线程编程(4)--线程同步机制

    一.锁 1.锁的概念   线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...

  7. Java多线程 | 02 | 线程同步机制

    同步机制简介 ​ 线程同步机制是一套用于协调线程之间的数据访问的机制.该机制可以保障线程安全.Java平台提供的线程同步机制包括: 锁,volatile关键字,final关键字,static关键字,以 ...

  8. Java分享笔记:创建多线程 & 线程同步机制

    [1] 创建多线程的两种方式 1.1 通过继承Thread类创建多线程 1.定义Thread类的子类,重写run()方法,在run()方法体中编写子线程要执行的功能. 2.创建子线程的实例对象,相当于 ...

  9. (转)Java并发编程:深入剖析ThreadLocal

    Java并发编程:深入剖析ThreadLoca Java并发编程:深入剖析ThreadLocal 说下自己的理解:使用ThreadLocal能够实现空间换时间,重在理解ThreadLocal是如何复制 ...

随机推荐

  1. [MySQL Reference Manual] 6 安全性

    6. 安全性 在Mysql安装配置时要考虑安全性的影响,以下几点: Ÿ   常规因素影响安全性 Ÿ   程序自身安全性 Ÿ   数据库内部的安全性,即,访问控制 Ÿ   网络安全性和系统安全性 Ÿ   ...

  2. Mongodb Manual阅读笔记:CH4 管理

    4 管理 Mongodb Manual阅读笔记:CH2 Mongodb CRUD 操作Mongodb Manual阅读笔记:CH3 数据模型(Data Models)Mongodb Manual阅读笔 ...

  3. ASP.NET Web API 简介

    ASP.NET MVC 4 包含了 ASP.NET Web API, 这是一个创建可以连接包括浏览器.移动设备等多种客户端的 Http 服务的新框架, ASP.NET Web API 也是构建 RES ...

  4. cocos2d之z轴位置示例

    1:设置资源本地z轴位置 2:调整位置示例 3:调整两个资源为同一父节点 4:ImageView 设置资源,此赋值方法第一个为资源路径

  5. 烂泥:CentOS命令学习之scp复制

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 由于工作需要,需要把服务器A上的文件弄一份到服务器B上.自己比较懒不打算搭建FTP.Samba服务器,所以就打算使用scp命令,scp命令是通过ssh协 ...

  6. JAVA-android 更改APP名称与图标

    首先要在你的资源文件放入你想换的图标图片拖到drawable-XX文件夹下,然后你打开AndroidManifest.xml这个配置清单文件找到application标签里的这句android:ico ...

  7. AIR ANE(本机扩展)使用中的一些问题(Android平台)

    关于如何写ANE,就不说了,用关键字,Android ANE 开发,会搜索到N多. 下面写一下碰到的问题,和一些别人可能没有说清的地方 1. 生成的ANE是直接拷到lib里使用吗?A:这个一定不要直接 ...

  8. raspberry pi2 智能小车源码及测试视频

    作者:XIAOBO QQ:463431476 转载请注明作者Python 源代码 import RPi.GPIO as GPIO  #human-computer-interaction import ...

  9. iframe 的使用和登陆退出的实现——整个页面跳转

    iframe中如果只是页面跳转的话,我们依然只是部分的加载的了,为了实现整个页面的所有内容跳转,下面提供了整个页面跳转的方法. iframe例子 1.总的iframe页面(访问就访问这个)  all. ...

  10. emacs 新手笔记(二) —— 分割窗格 (split window)

    初极狭,才通人.复行数十步,豁然开朗.—— 陶渊明·桃花源记 ilocker:关注 Android 安全(新入行,0基础) QQ: 2597294287 使用 split-window-xxx 函数可 ...