22.2 线程安全及性能:ThreadLocal(❤❤)

本文介绍了如何使用ThreadLocal解决线程安全问题,如创建大量线程时的SimpleDateFormat对象重复创建,以及如何通过ThreadLocal实现线程独享对象以提高性能。同时讨论了内存泄露的注意事项。
摘要由CSDN通过智能技术生成

1. 简介

1.1 使用场景

在这里插入图片描述

2. ThreadLocal实现线程独享对象

在这里插入图片描述

2.1 基于SimpleDateFormat讲解ThreadLocal优势

解决:常用对象的反复创建以及线程安全问题

1. 2个线程分别使用SimpleDateFormat对象

在这里插入图片描述
在这里插入图片描述

2. 10个线程甚至更多使用SimpleDateFormat对象

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. 基于线程池使用SimpleDateFormat对象

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
问题:1000个线程任务,对应意味着系统创建了1000次SimpleDateFormat对象

4. 优化SimpleDateFormat对象反复创建

在这里插入图片描述
在这里插入图片描述
问题:出现了重复的时间
当所有线程共用同一个SimpleDateFormat对象时发生了线程安全问题
在这里插入图片描述

5. 加锁解决4的线程安全问题

在这里插入图片描述
加锁,导致线程排队一个一个的执行,效率极低

6. ThreadLocal方式(❤❤)

在这里插入图片描述
在这里插入图片描述
代码分析:
在类中创建ThreadLocal类,重写initialValue初始化方法
使用:在线程中调用ThreadLocal对象.get()方法获取独享对象

package threadlocal;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:     利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全,高效利用内存
 */
public class ThreadLocalNormalUsage05 {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalNormalUsage05().date(finalI);
                    System.out.println(date);
                }
            });
        }
        threadPool.shutdown();
    }

    public String date(int seconds) {
        //参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
        Date date = new Date(1000 * seconds);
//        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();
        System.out.println(
                Thread.currentThread().getName() + ThreadSafeFormatter.dateFormatThreadLocal);
        SimpleDateFormat simpleDateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
        System.out.println(Thread.currentThread().getName() + simpleDateFormat.toString());
        System.out.println(
                Thread.currentThread().getName() + System.identityHashCode(simpleDateFormat));

        return dateFormat.format(date);
    }
}

class ThreadSafeFormatter {

    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        //initialValue初始化方法
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    /**
     * 基于Lambda表达式书写
     */
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal
            .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}

7. 小结

在这里插入图片描述
在这里插入图片描述

3. ThreadLocal创建当前线程内的全局变量

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.1 一般想法:使用UserMap保存

在这里插入图片描述
多线程时读取会有线程安全问题,加锁又影响性能
在这里插入图片描述

3.2 ThreadLocal实现

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.3 代码演示

1.基于ThreadLocal创建线程全局变量:

public static ThreadLocal<T> holder = new ThreadLocal<>();

在这里插入图片描述
2.线程业务时设置(set)变量到全局变量中
在这里插入图片描述
3.获取(get)当前线程全局变量内容
在这里插入图片描述
完整代码:

package threadlocal;

/**
 * 描述:     演示ThreadLocal用法2:避免传递参数的麻烦
 */
public class ThreadLocalNormalUsage06 {

    public static void main(String[] args) {
        new Service1().process("");
    }
}

class Service1 {
    public void process(String name) {
        User user = new User("超哥");
        UserContextHolder.holder.set(user);
        new Service2().process();
    }
}

class Service2 {
    public void process() {
        User user = UserContextHolder.holder.get();
        System.out.println("Service2拿到用户名:" + user.name);
        new Service3().process();
    }
}

class Service3 {
    public void process() {
        User user = UserContextHolder.holder.get();
        System.out.println("Service3拿到用户名:" + user.name);
        UserContextHolder.holder.remove();
    }
}

class UserContextHolder {

    public static ThreadLocal<User> holder = new ThreadLocal<>();


}

class User {

    String name;

    public User(String name) {
        this.name = name;
    }
}

4. ThreadLocal主要方法

4.1 initialValue方法:初始化

在这里插入图片描述
在这里插入图片描述

     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.:
返回当前线程的“初始值”线程局部变量。
此方法将首先调用线程使用{@link#get}访问变量的时间方法,
除非线程先前调用了{@link#集}方法,在这种情况下,{@code initialValue}方法将不会为线程调用。
通常,此方法在每个线程最多调用一次,
但在以下情况下可能会再次调用随后调用{@link#remove},然后调用{@link#get}<p>这个实现只返回{@code-null};
如果程序员希望线程局部变量有一个初始值{@code null}、{@code ThreadLocal}以外的值必须为子类化,并且该方法被重写。
通常
将使用匿名内部类。

在这里插入图片描述
应用:
在这里插入图片描述
在这里插入图片描述

4.2 set与get方法

在这里插入图片描述

4.3 remove方法

在这里插入图片描述
在这里插入图片描述

5. ThreadLocal源码分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

6. 注意事项

6.1 内存泄露

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

****************************************************************************************************************

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与海boy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值