文章目录:
写在前面
除了控制资源的访问外,还可以通过增加资源来保证线程安全。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了。
下面这张图是上面完整代码的运行结果,可以看到为这两个子线程分别设定了一个指定的时间。