Java——多线程高并发系列之ThreadLocal的使用

Posted 张起灵-小哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java——多线程高并发系列之ThreadLocal的使用相关的知识,希望对你有一定的参考价值。

文章目录:

写在前面

Demo1

Demo2

Demo3


写在前面

除了控制资源的访问外,还可以通过增加资源来保证线程安全。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)的理解与使用

Java——多线程高并发系列之ThreadLocal的使用

Java——多线程高并发系列之ThreadLocal的使用

Java——多线程高并发系列之LockReentrantLock

Java——多线程高并发系列之LockReentrantLock