Java——多线程高并发系列之ThreadLocal的使用
Posted 张起灵-小哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java——多线程高并发系列之ThreadLocal的使用相关的知识,希望对你有一定的参考价值。
文章目录:
写在前面
除了控制资源的访问外,还可以通过增加资源来保证线程安全。ThreadLocal 主要解决为每个线程绑定自己的值。
Demo1
package com.szh.threadlocal;
/**
* ThreadLocal 的基本使用
*/
public class Test01 {
//定义一个ThreadLocal对象
static ThreadLocal threadLocal=new ThreadLocal();
//定义线程类
static class SubThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
//设置线程关联的值
threadLocal.set(Thread.currentThread().getName() + " ---> " + i);
//读取线程关联的值
System.out.println(Thread.currentThread().getName() + " value ---> " + threadLocal.get());
}
}
}
public static void main(String[] args) {
SubThread t1=new SubThread();
SubThread t2=new SubThread();
t1.start();
t2.start();
}
}
这里定义了两个子线程,之后我为每个子线程都设置了5个关联的值(set方法),然后可以通过(get方法)获取到为每个子线程设置的关联值。
Demo2
package com.szh.threadlocal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 在多线程环境中,把字符串转换为日期对象,多个线程使用同一个 SimpleDateFormat 对象
* 可能会产生线程安全问题,有异常
* 为每个线程指定自己的 SimpleDateFormat 对象, 使用 ThreadLocal
*/
public class Test02 {
//定义ThreadLocal对象
private static ThreadLocal<SimpleDateFormat> threadLocal=new ThreadLocal<>();
static class ParseDate implements Runnable {
private int i=0;
public ParseDate(int i) {
this.i=i;
}
@Override
public void run() {
String test="2021年6月8日 13:14:" + i%60; //构建日期字符串
//先判断当前线程是否有 SimpleDateFormat 对象,如果当前线程没有 SimpleDateFormat 对象就创建一个,如果有就直接使用
if (threadLocal.get() == null) {
threadLocal.set(new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"));
}
try {
Date date=threadLocal.get().parse(test);
System.out.println(i + " -- " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//创建10个线程,每个线程都有自己对应的日期
for (int i = 0; i < 10; i++) {
new Thread(new ParseDate(i)).start();
}
}
}
ThreadLocal在new对象的时候,是可以有一个泛型的,这里我将泛型定义为 SimpleDateFormat 这个日期格式化类。然后我创建了10个子线程,为每个子线程都设置一个它们各自的时间。
那么这里先判断当前线程是否有 SimpleDateFormat 对象,如果当前线程没有 SimpleDateFormat 对象就创建一个;如果有就直接调用get方法使用(get方法返回的是一个泛型T),所以这里调用get方法返回的就是 SimpleDateFormat,之后再通过parse将test字符串转为日期。
Demo3
package com.szh.threadlocal;
import java.util.Date;
/**
* ThreadLocal 初始值, 第一次调用threadLocal的get()方法会返回null
* 解决方法:
* 定义 ThreadLocal 类的子类,
* 在子类中重写 initialValue() 方法指定初始值,再第一次调用 get()方法不会返回 null
*/
public class Test03 {
//定义ThreadLocal
static ThreadLocal threadLocal=new SubThreadLocal();
//定义ThreadLocal的子类,同时重写 initialValue() 方法
static class SubThreadLocal extends ThreadLocal<Date> {
@Override
protected Date initialValue() {
//将初始值设置为15分钟之前(单位是ms,1000ms=1s,*60=1分钟,*15=15分钟)
return new Date(System.currentTimeMillis() - 1000*60*15);
}
}
//定义线程类
static class SubThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("-----" + Thread.currentThread().getName() + " value= "
+ threadLocal.get());
//如果没有初始值,就设置当前日期
if (threadLocal.get() == null) {
threadLocal.set(new Date());
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
SubThread t1=new SubThread();
SubThread t2=new SubThread();
t1.start();
t2.start();
}
}
将上面代码中的静态内部类注释掉,然后将代码的第14行修改为:static ThreadLocal threadLocal=new ThreadLocal(); 就会出现下面这张图的运行结果。
原因是在最初的时候,如果当前线程的ThreadLocal对象没有set了话,那么get到的值将会是null。
上面那张图是不是最开始的value是null,那么解决方法就是:定义一个ThreadLocal的子类,然后重写 initialValue 方法,在这里面指定初始值,之后再调用get方法就不会得到null了。
下面这张图是上面完整代码的运行结果,可以看到为这两个子线程分别设定了一个指定的时间。
以上是关于Java——多线程高并发系列之ThreadLocal的使用的主要内容,如果未能解决你的问题,请参考以下文章
Java——多线程高并发系列之线程池(Executor)的理解与使用
Java——多线程高并发系列之线程池(Executor)的理解与使用