博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
谈话Java在ThreadLocal理解类
阅读量:7202 次
发布时间:2019-06-29

本文共 5337 字,大约阅读时间需要 17 分钟。

我们必须先了解:ThreadLocal不超过一个线程类,或者它应该被称为线程局部变量。这从ThreadLocal的JDK我们可以看到的定义

public class ThreadLocal
extends Object

能够看出ThreadLocal仅仅是一个普普通通的类,并没有继承自Thread或实现Runnable接口。

同一时候也能够看到ThreadLocal使用了泛型。这样他就能够操作差点儿不论什么类型的数据了。

以下说JDK API代码时详细再说。

 

对此类,看看JDK API中的部分描写叙述:

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通相应物,由于訪问某个变量(通过其 get 或 set 方法)的每一个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例一般是类中的 private static 字段,它们希望将状态与某一个线程(比如,用户 ID 或事务 ID)相关联。

 

1、ThreadLocal不是线程。而是县城的一个局部变量

2、再使用时ThreadLocal通常作为类中的一个private static变量使用

3、每一个线程都有一个自己的局部变量,之间互不冲突,对变量值的操作互不影响,不存在资源冲突。

整体来说。ThreadLocal作为线程的工具,为每个线程独立的提供了一个存储与訪问线程变量的方式,使得不同线程间存储的数据互不影响(因此不会出现资源竞争问题)。

 

以下就说说ThreadLocal是怎样作为一个线程工具,为每一个线程提供存储与訪问线程局部变量的。

ThreadLocal提供了下面几个API用于存取线程局部变量:

 

()
          返回此线程局部变量的当前线程副本中的值。

protected  ()
          返回此线程局部变量的当前线程的初始值。
void ()
          移除此线程局部变量的值。
void ( value)
          将此线程局部变量的当前线程副本中的值设置为指定值。

 

能够看到每一个都是泛型类型的。

接口的工作原理以下会详细介绍。

 

ThreadLocal类中还提供了一个静态的内部类:

 static class ThreadLocalMap

我们能够把这个ThreadLocalMap看做是一个普通的MAP类型,能够保存一个键值对就可以,这样便于理解。

这个ThreadLocalMap在ThreadLocal中定义,但却在线程类java.lang.Thread中使用。在Thread类中有下面定义:

ThreadLocal.ThreadLocalMap threadLocals = null

言外之意就是Thread类中有一个类似于Map的ThreadLocal.ThreadLocalMap 类型的变量,能够用于存储键值对类型的值。

 

以下上一个简单的样例。依据输出结果。分析Thread与ThreadLocal工作过程。

类SerialNum

public class SerialNum {	// The next serial number to be assigned    private static int nextSerialNum = 0;    public static ThreadLocal serialNum = new ThreadLocal() {        protected synchronized Object initialValue() {            return new Integer(nextSerialNum++);        }    };    public static int get() {        return ((Integer) (serialNum.get())).intValue();    }    public static void set(int val){    	serialNum.set(val);    }}

 

类SerialNum中定义了一个private static ThreadLocal 类型的匿名内部类serialNum变量,事实上全然能够直接new ThreadLocal,这样写仅仅是为了实现重写其initialValue方法。由于在ThreadLocal中initialValue()方法返回一个NULL值。须要说明的是,initialValue()方法是一个懒载入方法,被ThreadLocal类内部调用。而且仅仅在第一次threadLocal类的get()被调用时调用。重写它是为了让局部变量有一个初始值。

SerialNum类的静态get()和set()方法都调用ThreadLocal类的get()和set()方法。

 

类:ThreadTest

public class ThreadTest extends Thread{	@Override	public void run() {		// TODO Auto-generated method stub		//先打印初始值		System.out.println(Thread.currentThread().getName()+":"+SerialNum.get());		//当前值加4		SerialNum.set(SerialNum.get()+4);		//打印加4后的值		System.out.println(Thread.currentThread().getName()+":"+SerialNum.get());	}	public static void main(String[] args) {		// TODO Auto-generated method stub		ThreadTest t = new ThreadTest();		ThreadTest t2 = new ThreadTest();		ThreadTest t3 = new ThreadTest();		t.start();		t2.start();		t3.start();	}}

 

ThreadTest中的run方法中调用了SerialNum类的静态get()和set()方法。

输出结果例如以下:

Thread-2:2

Thread-0:0
Thread-1:1
Thread-2:6
Thread-0:4
Thread-1:5

以下我们開始分析为什么会出现这种结果。我们就拿线程Thread-0来说,它相应的就是t.start()启动的线程

1、当t线程先执行时(thread-0),会调用SerialNum.get(),而其会再调用threadLocal的get方法。

ThreadLocal的get()方法例如以下:

public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            return (T)map.get(this);        // Maps are constructed lazily.  if the map for this thread        // doesn't exist, create it, with this ThreadLocal and its        // initial value as its only entry.        T value = initialValue();        createMap(t, value);        return value;    }

get()方法中先获得当前线程,然后调用getMap(),API方法例如以下:

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

 

getMap()返回当前线程中的 threadLocals变量。前面我们说过,Thread中定义了一个ThreadLocal.ThreadLocalMap类型的变量。就是这个了。

然后在get()方法中。检查map为不为NUll。假设是NULL值,所以会调用initialValue()。而这种方法被我们重写了。它返回了SerialNum类中定义的静态变量nextSerialNum当前值,然后将nextSerialNum+1。由于nextSerialNum初始为0,所以返回的是0。然后调用createMap(t,value)。

这个API内容例如以下:

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

能够看到 createMap中创建了一个ThreadLoadMap对象。并复制给当前线程的threadLocals变量。新创建的ThreadLocalMap类中保存了局部变量的值value,当中键为this,即我们在SerialNum类中定义的那个private static ThreadLocal serialNum。

最后get()方法返回初始化好的value值0。

同一时候

以上就是thead-0第一次输出打印出0的原因。

2、当线程thread-0执行到SerialNum.set(SerialNum.get()+4);这一句时,我们看发生了什么

首先SerialNum.get()+4会取出threadLocal变量serialNum中保存的线程局部变量值,回头看看ThreadLocal.get()方法的介绍,由于此时当前线程中已经有了ThreadLocalMap 类型的threadLocals,所以会直接调用map.get(this);当中this依旧是我们在SerialNum类中定义的那个private static ThreadLocal serialNum。

这样就返回了map中的值。

然后+4后调用ThreadLocal的set()方法。我们看看set()方法的JDK内容:

public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }

能够看到set()方法首先也是得到当前线程,从当前线程中获取threadLocalMap类型的map。假设map不为null,就将value保存进去。假设为null。就在createMap中new一个ThreadLocalMap,保存value。然后将这个 新new的threadLocalMap类型的对象赋值给当前线程的threadLocals变量(详见createMap()方法)。

经过执行后,局部变量值就等于0+4=4了。

这也是为什么第二次打印出的值为thread-0:4。

 

再简单说说第二个线程thread-1。第二个线程启动时,由于静态变量nextSerialNum已经为1(被线程thread-0读取后++)。所以他是从1開始打印。而且后面的+4操作与线程thread-0和thread-2并不冲突。由于:

thread-0这个线程的局部变量被保存进threadLocalMap后,这个threadLocalMap变量被复制给这个线程(也就是thread-0自己)的threadLocals变量了。相同的。thread-1;thread-2这两个线程的局部变量保存进threadLocalMap后,都分别复制给自己这个线程的threadLocals变量了。所以以后再操作都是从自己线程里面取threadLocalMap。当然互不影响。

相同的一点是这三个线程的threadLocalMap中的KEY都为同一个ThreadLocal对象。

通过这个样例还能看出一点,局部变量初始化后,就再和開始那个nextSerialNum没关系了。这应该就是JDK API中描写叙述的:訪问一个变量(通过其 getset 方法)的每一个线程都有自己的局部变量,它独立于变量的初始化副本

以上就是自己对ThreadLocal使用的浅谈。開始怕说不清楚。就想写的直白点。后来自己都感觉啰嗦了大笑

 

版权声明:本文博客原创文章。博客,未经同意,不得转载。

你可能感兴趣的文章
Linux中目录操作命令
查看>>
Linux下的touch命令及时间戳
查看>>
安装如Epson LQ-300K等老式打印机方法和心得
查看>>
菜鸟的第一次编程感受
查看>>
对代码命名的一点思考和理解
查看>>
shell监测mysql是否启动
查看>>
redis的单机安装与配置以及生产环境启动方案
查看>>
centos 安装raid驱动及部署实战
查看>>
程序员保值的4个秘密
查看>>
apache安装完成后,添加模块
查看>>
Linux中查看CPU信息
查看>>
网络性能测试工具Iperf介绍
查看>>
基本粒子群优化算法(PSO)的matlab实现
查看>>
我的友情链接
查看>>
go 生成随机字符串和获得定长字符串
查看>>
设置固定IP
查看>>
Sent Items 不见了
查看>>
Style3D属性面板的开发指南
查看>>
Mac OS X 从零开始系列教程10-刻录光盘
查看>>
对学习计算机专业的人一些忠告
查看>>