并发编程学习笔记(8)----ThreadLocal的使用及源码分析

时间:2022-12-13 09:59:56

1. ThreadLocal的理解

  ThreadLocal,顾名思义,就是线程的本地变量,ThreadLocal会为每个线程创建一个本地变量副本,使得使用ThreadLocal管理的变量在多线程的环境下,每个线程都是访问的是自己内部的副本变量,将全局变量局部化,跟局部变量一样的使用,从而避免了一些线程安全的问题。比如在数据库连接池中获取的连接时,此时的连接对于每个线程来说,都应该是独立的,所以可以使用ThreadLocal来管理该线程的连接。从而避免在多线程环境下的连接的安全问题。

2. ThreadLocal的使用方式

  ThreaLocal的使用方式也是非常简单的,直接创建一个全局的ThreadLocal,并初始化,这里需要注意的是直接new ThreadLocal时这里ThreadLocal管理的对象是null的,所以如果需要在创建对象的时候就初始化对象的话,可以重写ThreadLocal中的InitialValue方法来进行初始化,初始化完成之后,就是使用了,调用ThreadLocal的get方法拿到被管理的对象,操作对象,再将修改后的对象通过ThreadLocal的set方法保存到ThreadLocal中。代码如下:

public class ThreadLoclDemo {

    private ThreadLocal<Integer> nextc = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return Integer.valueOf(0);
}
}; public int getNext () {
Integer integer = nextc.get();
integer++;
nextc.set(integer);
return integer;
} public static void main(String[] args) {
final ThreadLoclDemo threadLoclDemo = new ThreadLoclDemo();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + " " + threadLoclDemo.getNext());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + " " + threadLoclDemo.getNext());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + " " + threadLoclDemo.getNext());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + " " + threadLoclDemo.getNext());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}

  这里通过五个线程去执行getNext()方法,输出如下:

Thread-0 1
Thread-1 1
Thread-2 1
Thread-3 1
Thread-1 2
Thread-2 2
Thread-0 2
Thread-3 2
Thread-1 3
Thread-0 3
Thread-2 3
Thread-2 4
Thread-1 4
Thread-0 4
Thread-3 3
Thread-0 5
Thread-2 5
Thread-1 5
Thread-2 6
Thread-1 6
Thread-0 6
Thread-3 4
Thread-2 7
Thread-0 7
Thread-1 7
Thread-1 8

  可以看出,对于每个线程,它们锁获取到的值都是自增1之后的值,并没有出现线程安全问题或是线程之间由于操作共享数据而相互影响的情况,输出犹如线程在使用局部变量一样清晰,这就是ThreadLocal的使用方式和作用。

 3.ThreadLocal的源码分析

  ThreadLocal的源码还是比较简单的,它的代码之中我们只需要关注get()/set()/remove()三个方法即可

  首先看set()方法:

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

  首先获取当前线程,通过getMap()传入当前线程去获取ThreadLocalMap,ThreadLocalMap属于Thread的一个内部属性threadLocals,所以通过当前线程就可以获取当前线程所对应的ThreadLocalMap,如果当前map不为空,表示当前线程已经被创建,则以当前线程为key,修改value为值,保存变量,如果为空,则通过createMap()新建一个ThreadLocalMap对象,并将其赋值给当前Thread对象的threadLocals属性。这就完成了set()的操作。

  get()方法:

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}

  获取当前线程,通过当前线程获取ThreadLocalMap,如果map为空,则返回setInitialValue()方法,这里此方法返回的是null,所以在最开始初始化时,被管理的对象的值为null也是这个原因,可以重写initialValue()方法来进行初始化。如果map不为空,则通过map中的getEntry()方法获取ThreadLocalMap中的entry对象,所以ThreadLocal管理的对象实际上时存储在ThreadLocalMap的Entry内部类中的value中的,如果entry存在,直接返回e.value,也就是ThreadLocal管理的对象,否则返回初始化的值(null)。

  remove()方法:

 public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

  remove()方法很简单,获取当前线程的ThreadLocalMap,不为空则调用ThreadLocalMap的remove()方法。直接移除当前ThreadLocalMap中的Entry对象,即移除了当前线程和ThreadLocal为当前线程创建的内部副本。

  ThreadLocal的分享就到这里,不足之处,请各位多多指教。

原文 并发编程学习笔记(8)----ThreadLocal的使用及源码分析

并发编程学习笔记(8)----ThreadLocal的使用及源码分析的更多相关文章

  1. 并发编程学习笔记&lpar;9&rpar;----AQS的共享模式源码分析及CountDownLatch使用及原理

    1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从*接口acquireShared()方法入手: public fin ...

  2. 并发编程学习笔记&lpar;6&rpar;----公平锁和ReentrantReadWriteLock使用及原理

    (一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...

  3. Java并发编程学习笔记

    Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...

  4. 并发编程学习笔记&lpar;15&rpar;----Executor框架的使用

    Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...

  5. 并发编程学习笔记&lpar;14&rpar;----ThreadPoolExecutor(线程池)的使用及原理

    1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...

  6. 并发编程学习笔记&lpar;13&rpar;----ConcurrentLinkedQueue&lpar;非阻塞队列&rpar;和BlockingQueue&lpar;阻塞队列&rpar;原理

    · 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...

  7. 并发编程学习笔记&lpar;11&rpar;----FutureTask的使用及实现

    1. Future的使用 Future模式解决的问题是.在实际的运用场景中,可能某一个任务执行起来非常耗时,如果我们线程一直等着该任务执行完成再去执行其他的代码,就会损耗很大的性能,而Future接口 ...

  8. 并发编程学习笔记&lpar;12&rpar;----Fork&sol;Join框架

    1. Fork/Join 的概念 Fork指的是将系统进程分成多个执行分支(线程),Join即是等待,当fork()方法创建了多个线程之后,需要等待这些分支执行完毕之后,才能得到最终的结果,因此joi ...

  9. 并发编程学习笔记&lpar;10&rpar;----并发工具类CyclicBarrier、Semaphore和Exchanger类的使用和原理

    在jdk中,为并发编程提供了CyclicBarrier(栅栏),CountDownLatch(闭锁),Semaphore(信号量),Exchanger(数据交换)等工具类,我们在前面的学习中已经学习并 ...

随机推荐

  1. C&plus;&plus; Primer Pluse&lowbar;7&lowbar;课后题

    #include <iostream> using namespace std; double Sum2(double x, double y) { double sum = 0; if ...

  2. 压力测试之TCPP

    1.下载源码 tpcc-mysql-src.tgz 2.解压 tpcc-mysql-src.tgz 3.安装 [root@DBMysql mysql]# cd /home/mysql/tpcc-mys ...

  3. 转】MyEclipse使用总结——MyEclipse中配置WebLogic12c服务器

    原博文出自于:http://www.cnblogs.com/xdp-gacl/p/4142495.html 感谢! MyEclipse中配置WebLogic12c服务器的步骤如下: [Window]→ ...

  4. Cocos2d-JS中的精灵菜单和图片菜单

    精灵菜单的菜单项类是cc.MenuItemSprite,图片菜单的菜单项类是cc.MenuItemImage.由于cc.MenuItemImage继承于cc.MenuItemSprite,所以图片菜单 ...

  5. 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。 n&lt&semi;&equals;39

    // test14.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include<iostream> #include&lt ...

  6. JDBC之修改数据

    文件分布图: 在MySQL中设置表格: Books: package com.caiduping.entity; public class Books { private int id; // 图书名 ...

  7. 防止 DDoS 攻击的五个「大招」!

    提到 DDoS 攻击,很多人不会陌生.上周,美国当地时间 12 月 29 日,专用虚拟服务器提供商 Linode 遭到 DDoS 攻击,直接影响其 Web 服务器的访问,其中 API 调用和管理功能受 ...

  8. R语言预测实战&lpar;游浩麟&rpar;笔记2

    特征构建技术 特征变换,对原始的某个特征通过一定的规则或映射得到新特征的方法,主要方法包括概念分层.标准化.离散化.函数变换以及深入表达.特征变换主要由人工完成,属于比较基础的特征构建方法. 概念分层 ...

  9. go tcp

    TCP编程 1.客户端和服务器 2.服务端的处理流程 监听端口 接收客户端的链接 创建goroutine,处理该链接 3.客户端的处理流程 建立与服务端的链接 进行数据收发 关闭链接 服务端代码 pa ...

  10. 机器学习技法笔记:05 Kernel Logistic Regression

    Roadmap Soft-Margin SVM as Regularized Model SVM versus Logistic Regression SVM for Soft Binary Clas ...