当前位置:网站首页>ThreadLocal还不会?来看看!
ThreadLocal还不会?来看看!
2022-06-09 12:40:00 【little-peter】
ThreadLocal设计是为了解决并发时,线程共享变量的问题,由于过度设计,如弱引用和哈希碰撞,导致其令人难以理解和使用成本高等问题。除此之外,使用稍有不慎还会造成脏数据和内存泄漏,共享变量更新等问题。但即便如此,ThreadLocal依然有自己的适用场景,以及无可取代的价值,比如接下来要介绍的这两种使用场景,除了
ThreadLocal之外,还真没有合适的替代方案。
使用场景1:本地变量
我们以多线程格式化时间为例,来演示 ThreadLocal 的价值和作用,当我们在多个线程中格式化时间时,通常会这样操作。
- 当有两个线程进行时间格式化时,我们可以这样写:
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
public static void main(String[] args) throws InterruptedException {
// 创建并启动线程1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 得到时间对象
Date date = new Date(1 * 1000);
// 执行时间格式化
formatAndPrint(date);
}
});
t1.start();
// 创建并启动线程2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 得到时间对象
Date date = new Date(2 * 1000);
// 执行时间格式化
formatAndPrint(date);
}
});
t2.start();
}
/**
* 格式化并打印结果
* @param date 时间对象
*/
private static void formatAndPrint(Date date) {
// 格式化时间对象
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
// 执行格式化
String result = simpleDateFormat.format(date);
// 打印最终结果
System.out.println("时间:" + result);
}
}以上程序运行结果为:

上面代码因为创建的线程数量不多,所以我们可以给每个线程创建一个私有对象 SimpleDateFormat来进行时间格式化。
2.10个线程进行时间格式化
当有10个线程进行时间格式化时,我们可以使用for循环创建多个线程执行时间格式化,具体代码如下:
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
int finalI = i;
// 创建线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 得到时间对象
Date date = new Date(finalI * 1000);
// 执行时间格式化
formatAndPrint(date);
}
});
// 启动线程
thread.start();
}
}
/**
* 格式化并打印时间
* @param date 时间对象
*/
private static void formatAndPrint(Date date) {
// 格式化时间对象
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
// 执行格式化
String result = simpleDateFormat.format(date);
// 打印最终结果
System.out.println("时间:" + result);
}
}以上程序执行结果为:

从上述结果可以看出,虽然此时创建的线程数和 SimpleDateFormat 的数量不算少,但程序还是可以正常运行的。
3.1000个线程格式化
当我们将线程的数量从 10 个变成 1000 个的时候,我们就不能单纯的使用 for 循环来创建 1000 个线程的方式来解决问题了,因为这样频繁的新建和销毁线程会造成大量的系统开销和线程过度争抢 CPU 资源的问题。所以经过一番思考后,我们决定使用线程池来执行这 1000 次的任务,因为线程池可以复用线程资源,无需频繁的新建和销毁线程,也可以通过控制线程池中线程的数量来避免过多线程所导致的 CPU 资源过度争抢和线程频繁切换所造成的性能问题,而且我们可以将 SimpleDateFormat 提升为全局变量,从而避免每次执行都要新建 SimpleDateFormat 的问题,于是我们写下了这样的代码:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class App {
// 时间格式化对象
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
public static void main(String[] args) throws InterruptedException {
// 创建线程池执行任务
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
for (int i = 0; i < 1000; i++) {
int finalI = i;
// 执行任务
threadPool.execute(new Runnable() {
@Override
public void run() {
// 得到时间对象
Date date = new Date(finalI * 1000);
// 执行时间格式化
formatAndPrint(date);
}
});
}
// 线程池执行完任务之后关闭
threadPool.shutdown();
}
/**
* 格式化并打印时间
* @param date 时间对象
*/
private static void formatAndPrint(Date date) {
// 执行格式化
String result = simpleDateFormat.format(date);
// 打印最终结果
System.out.println("时间:" + result);
}
}以上程序执行结果为:

上面结果说明我们的代码是线程不安全的。
线程安全问题:在多线程的执行中,程序的执行结果与预期结果不相符的情况。
- 为什么会出现上述问题呢?
为了找到问题所在,我们尝试查看 SimpleDateFormat 中 format 方法的源码来排查一下问题,format 源码如下:
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// 注意此行代码
calendar.setTime(date);
从上述源码可以看出,在执行 SimpleDateFormat.format 方法时,会使用 calendar.setTime 方法将输入的时间进行转换,那么我们想象一下这样的场景:
- 线程 1 执行了
calendar.setTime(date)方法,将用户输入的时间转换成了后面格式化时所需要的时间; - 线程 1 暂停执行,线程 2 得到
CPU时间片开始执行; - 线程 2 执行了
calendar.setTime(date)方法,对时间进行了修改; - 线程 2 暂停执行,线程 1 得出
CPU时间片继续执行,因为线程 1 和线程 2 使用的是同一对象,而时间已经被线程 2 修改了,所以此时当线程 1 继续执行的时候就会出现线程安全的问题了。
- 解决线程安全问题:加锁
当出现线程安全问题时,我们想到的第一解决方案就是加锁,具体的实现代码如下:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class App {
// 时间格式化对象
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
public static void main(String[] args) throws InterruptedException {
// 创建线程池执行任务
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
for (int i = 0; i < 1000; i++) {
int finalI = i;
// 执行任务
threadPool.execute(new Runnable() {
@Override
public void run() {
// 得到时间对象
Date date = new Date(finalI * 1000);
// 执行时间格式化
formatAndPrint(date);
}
});
}
// 线程池执行完任务之后关闭
threadPool.shutdown();
}
/**
* 格式化并打印时间
* @param date 时间对象
*/
private static void formatAndPrint(Date date) {
// 执行格式化
String result = null;
// 加锁
synchronized (App.class) {
result = simpleDateFormat.format(date);
}
// 打印最终结果
System.out.println("时间:" + result);
}
}加锁的缺点
加锁的方式虽然可以解决线程安全的问题,但同时也带来了新的问题,当程序加锁之后,所有的线程必须排队执行某些业务才行,这样无形中就降低了程序的运行效率了。
有没有既能解决线程安全问题,又能提高程序的执行速度的解决方案呢?
有的,这个时候 ThreadLocal就要上场了。
好菜在这里:https://blog.csdn.net/qq_49425839/article/details/117389318
参考王磊老师语雀
边栏推荐
猜你喜欢

云呐|行政单位固定资产管理制度,单位固定资产管理办法

Hit the snake seven inches

Arm architecture corresponding to commonly used chips in the market

网络七层结构是干啥的?看这篇文章就够了

使用nodejs导出md/Markdown文档当中的图片到本地并替换原始图片链接为本地图片链接

郑州埃文科技安全头条

5 minutes to learn about nfv

栈的基本方法及相关问题
![[C language practice - printing diamond and its deformation]](/img/ea/02c6ae5d178d47fe04b3550829f1bd.png)
[C language practice - printing diamond and its deformation]

记录下bilibili(b站)小火箭页面上划动画效果的实现
随机推荐
Analysis on the resumption of the most serious downtime in the history of Facebook on October 4, 2021
[C language practice - merging two ordered sequences]
Explain the three ways to remove duplicate data in MySQL
2022.6.7-----leetcode.875
CNN's performance is even stronger without looking at the whole picture and the parts
斯坦福博士提出超快省显存Attention,GPT-2训练速度提升3.5倍,BERT速度创纪录
2022.6.4-----leetcode.929
Explain asynchronous tasks in detail: the task of function calculation triggers de duplication
Yunna RFID asset management, advantages of RFID asset management system
【clickhouse专栏】单机版的安装与验证
At the age of 26, he published 18 papers and just proved the prime number conjecture of the last century
U8g2 graphics library and STM32 migration (I2C, software and hardware)
mysql中的delete,drop和truncate有什么区别
谁说Redis不能存大key
云呐|数据库监控一般监控什么
云呐|行政单位固定资产管理制度,单位固定资产管理办法
从最优化的角度看待Softmax损失函数
[signalr complete series] Realization of signalr real-time communication in net core
Install MySQL in MySQL installer mode
2022.6.3-----leetcode.829