Java ThreadLocal (Java代码实战-006)
ThreadLocal解决什么问题
由于 ThreadLocal 支持范型,如 ThreadLocal< StringBuilder >,为表述方便,后文用 变量 代表 ThreadLocal 本身,而用 实例 代表具体类型(如 StringBuidler )的实例。
不恰当的理解
写这篇文章的一个原因在于,网上很多博客关于 ThreadLocal 的适用场景以及解决的问题,描述的并不清楚,甚至是错的。下面是常见的对于 ThreadLocal的介绍
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路
ThreadLocal的目的是为了解决多线程访问资源时的共享问题
还有很多文章在对比 ThreadLocal 与 synchronize 的异同。既然是作比较,那应该是认为这两者解决相同或类似的问题。
上面的描述,问题在于,ThreadLocal 并不解决多线程 共享 变量的问题。既然变量不共享,那就更谈不上同步的问题。
合理的理解
ThreadLoal 变量,它的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal< String >而言即为 String 类型变量),在不同的 Thread 中有不同的副本(实际是不同的实例,后文会详细阐述)。这里有几点需要注意
因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来
既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题
既无共享,何来同步问题,又何来解决同步问题一说?
那 ThreadLocal 到底解决了什么问题,又适用于什么样的场景?
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
核心意思是
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。后文会通过实例详细阐述该观点。另外,该场景下,并非必须使用 ThreadLocal ,其它方式完全可以实现同样的效果,只是 ThreadLocal 使得实现更简洁。
ThreadLocal用法
java代码:
package Threads; import java.util.concurrent.CountDownLatch; /**
* Created by xfyou 2018/5/25 18:32.
*/
public class ThreadLocalDemo { // 闭锁需要等待的线程数量
private static final int THREADS_COUNT = 3; public static void main(String[] args) throws InterruptedException { /*在实时系统中的使用场景
*
* 实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。
* 例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用一次countDown()方法就可以让所有的等待线程同时恢复执行。
* 开始执行前等待N个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
* 死锁检测:一个非常方便的使用场景是,你可以使用N个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
*/
CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT); InnerClass innerClass = new InnerClass();
for (int i = 1; i <= THREADS_COUNT; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 4; j++) {
innerClass.add(String.valueOf(j));
innerClass.print();
}
innerClass.set("hello world"); /*
* 通知CountDownLatch对象,他们已经完成了各自的任务
* 每当一个线程完成了自己的任务后,计数器的值就会减1
* 所以当N个线程都调用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。
*/
countDownLatch.countDown();
}
}, "Thread-" + i).start();
} /*
* 主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
* CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。
* 每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁(Latch)上等待的线程就可以恢复执行任务。
*/
countDownLatch.await(); System.out.println("所有线程执行完毕");
System.out.println("主线程继续执行。。。");
} private static class InnerClass {
void add(String newStr) {
StringBuilder str = Counter.counter.get();
Counter.counter.set(str.append(newStr));
} void print() {
System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
Thread.currentThread().getName(),
Counter.counter.hashCode(),
Counter.counter.get().hashCode(),
Counter.counter.get().toString());
} void set(String words) {
Counter.counter.set(new StringBuilder(words));
System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
Thread.currentThread().getName(),
Counter.counter.hashCode(),
Counter.counter.get().hashCode(),
Counter.counter.get().toString());
}
} private static class Counter {
/*
* get时如果线程本地变量为null,则默认初始化一个这个变量类型的实例。
* StringBuilder为非线程安全的类型,通过ThreadLocal本地化则可以实现线程安全
*/
private static ThreadLocal<StringBuilder> counter = new ThreadLocal<StringBuilder>() {
@Override
protected StringBuilder initialValue() {
return new StringBuilder();
}
};
}
}
一种可能的运行结果如下:
Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:
Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:
Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:
Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:537578880, Value:
Set, Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:hello world Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:767376320, Value:
Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:
Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:
Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:
Set, Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:hello world Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:540065051, Value:
Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:
Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:
Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:
Set, Thread name:Thread- , ThreadLocal hashcode:, Instance hashcode:, Value:hello world 所有线程执行完毕
主线程继续执行。。。
运行结果分析:
1、所有线程访问的都是同一个ThreadLocal变量,其hashcode为:946838393(各线程访问的ThreadLocal在堆内存中的地址均为同一个);
2、各线程通过ThreadLocal对象的get()方法拿到的StringBuilder对象实例是不同的(hashcode不一样,实例在堆内存中的地址不一样);
3、各个线程将字符串追加进各自的 StringBuidler 实例内;
4、使用 set(T t) 方法后,ThreadLocal 变量所指向的 StringBuilder 实例被替换。
Java ThreadLocal (Java代码实战-006)的更多相关文章
- 通俗易懂详解Java代理及代码实战
一.概述 代理模式是Java常用的设计模式之一,实现代理模式要求代理类和委托类(被代理的类)具有相同的方法(提供相同的服务),代理类对象自身并不实现真正的核心逻辑,而是通过调用委托类对象的相关方法来处 ...
- Java秒杀系统实战系列~商品秒杀代码实战
摘要: 本篇博文是“Java秒杀系统实战系列文章”的第六篇,本篇博文我们将进入整个秒杀系统核心功能模块的代码开发,即“商品秒杀”功能模块的代码实战. 内容: “商品秒杀”功能模块是建立在“商品详情”功 ...
- linux中级-JAVA企业级应用TOMCAT实战
1. Tomcat简介 Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache.Sun和其他一些公司及个人共 ...
- [Java聊天室server]实战之二 监听类
前言 学习不论什么一个稍有难度的技术,要对其有充分理性的分析,之后果断做出决定---->也就是人们常说的"多谋善断":本系列尽管涉及的是socket相关的知识,但学习之前,更 ...
- [Java聊天室server]实战之五 读写循环(服务端)
前言 学习不论什么一个稍有难度的技术,要对其有充分理性的分析,之后果断做出决定---->也就是人们常说的"多谋善断":本系列尽管涉及的是socket相关的知识,但学习之前,更 ...
- java设计模式综合项目实战视频教程
java设计模式综合项目实战视频教程 视频课程目录如下: 第01节课:本课程整体内容介绍:X-gen系统概况,包括:引入.X-gen项目背景.X-gen的HelloWorld第02节课:X-gen整体 ...
- JAVA企业级应用TOMCAT实战
1. Tomcat简介 原文链接:https://blog.oldboyedu.com/java-tomcat/ Tomcat是Apache软件基金会(Apache Software Foundati ...
- Java ThreadLocal的使用
Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量.因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的Thread ...
- Java ThreadLocal 源代码分析
Java ThreadLocal 之前在写SSM项目的时候使用过一个叫PageHelper的插件 可以自动完成分页而不用手动写SQL limit 用起来大概是这样的 最开始的时候觉得很困惑,因为直接使 ...
随机推荐
- attrs.xml中declare-styleable 详解(用于自定义控件的属性)
1. 框架定义: <declare-styleable name = "名称"> <attr name = "……" format = &qu ...
- HTML5 本地文件操作之FileSystemAPI实例(三)
文件夹操作demo 1.读取根目录文件夹内容 window.requestFileSystem = window.requestFileSystem || window.webkitRequestFi ...
- Lp空间
在数学中,Lp空间是由p次可积函数组成的空间:对应的ℓp空间是由p次可和序列组成的空间.它们有时叫做勒贝格空间,以昂利·勒贝格命名(Dunford & Schwartz 1958,III.3) ...
- STM32学习笔记之EXTI(外部中断)
參考资料:STM32数据手冊.网络资料 =========================================切割线==================================== ...
- Java switch 枚举
Switch中能够使用int.byte.short,char,Enum,String.当中Enum为1.5之后新增特性,String为java8新增特性.本文介绍怎样在Switch中使用Enum类型. ...
- 整理:FPGA选型
针对性整理下FPGA选型问题 一.获取芯片资料: 要做芯片的选型,首先就是要对有可能要面对的芯片有整体的了解,也就是说要尽可能多的先获取芯片的资料.现在FPGA主要有4个生产厂家,ALTERA,XIL ...
- 【转】QT中QDataStream中浮点数输出问题
先上代码: C/C++ code ? 1 2 3 4 5 6 7 8 9 10 11 12 13 int main(int argc, char *argv[]) { QApplicati ...
- go语言之进阶篇recover的使用
1.recover的使用 示例: package main import "fmt" func testa() { fmt.Println("aaaaaaaaaaaaaa ...
- 以相声之名说android四大对象
当里个当,当里个当,Android此系统,易用有好用.谁为其奉献,只靠四巨头. 当里个当,当里个当,老大唤activity,界面缔造者.清水出芙蓉,天然来雕饰. 当里个当,当里个当,你若明白他,周期咋 ...
- Python 通过打码平台实现验证码
在爬虫时,经常遇到登录需要验证码的情况,简单的验证码可以自己解决,复制的验证码需要借助机器学习,有一定的难度.还有一个简单的方案就是采用付费的打码平台. 比如R若快(http://www.ruokua ...