ThreadLocal 的使用

本文详细介绍了ThreadLocal类在Java中的使用方法,包括解决线程间变量隔离的问题,避免共享变量冲突,以及如何设置默认值来避免get方法返回null等问题。

ThreadLocal 的使用

变量值的共享可以使用 public static 变量的形式,所有的线程都使用同一个 public static 变量。如果想实现每一个线程都有自己的共享变量该如何解决?JDK 中提供的类 ThreadLocal 正是为了解决这样的问题。

类 Thread Local 主要为了解决的就是每个线程绑定自己的值,可以将 ThreadLocal 类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据

方法 get() 和 null

public class ThreadLocal_Demo1 {
    public static ThreadLocal t1 = new ThreadLocal();

    public static void main(String[] args) {
        if(t1.get() == null){
            System.out.println("从未放过值");
            t1.set((int)(Math.random()*10));
        }
        System.out.println(t1.get());
        System.out.println(t1.get());
    }
}

image_1at3lhug7q4d1imqa6td6415f39.png-6.5kB

从上图中可以看到,第一次调用 t1 对象的 get() 方法时返回值是 null,通过调用 set() 方法赋值将值打印在控制台上。类 ThreadLocal 解决的是变量在不同线程间的隔离性,也就是不同的线程拥有自己的值,不同线程中的值是可以放入 ThreadLocal 中进行保存的。

验证线程隔离性

创建测试用的项目 ThreadLocalTest,类 Tools.java 代码如下
Tools 代码

public class Tools {
    public static ThreadLocal t1 = new ThreadLocal();
}

ThreadA 代码

public class ThreadA extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                Tools.t1.set("ThreadA "+i);
                System.out.println("Thread A get Value "+ Tools.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ThreadB 代码

public class ThreadB extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                Tools.t1.set("ThreadB "+i);
                System.out.println("Thread B get Value "+ Tools.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

主线程代码

public class mainThread {
    public static void main(String[] args) {
        try {
            Thread A = new ThreadA();
            Thread B = new ThreadB();
            A.start();
            B.start();
            for (int i = 0; i < 5; i++) {
                Tools.t1.set("main "+ i);
                System.out.println("Main get Value = " + Tools.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image_1at3mb5rmnpt1c6p1cuut3okrq13.png-15.4kB

程序运行结果如上所示。

虽然 3 个线程都向 t1 对象中 set() 数据值,但每个线程还是能够取出自己的数据。

创建新的项目,我们重新验证一次数据的隔离性
Tools 代码

import java.util.Date;

public class Tools {
    public static ThreadLocal<Date> t1 = new ThreadLocal<Date>();
}

ThreadA 代码

import java.util.Date;

public class ThreadA extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                if(Tools.t1.get() == null){
                    Tools.t1.set(new Date());
                }
                System.out.println("Thread A get Value "+ Tools.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ThreadB 代码

import java.util.Date;

public class ThreadB extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                if(Tools.t1.get() == null){
                    Tools.t1.set(new Date());
                }
                System.out.println("Thread B get Value "+ Tools.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

main 线程代码

public class mainThread {
    public static void main(String[] args) {
        try {
            Thread A = new ThreadA();
            Thread B = new ThreadB();
            A.start();
            B.start();
            for (int i = 0; i < 5; i++) {
                if(Tools.t1.get() == null){
                    Tools.t1.set(new Date());
                }
                System.out.println("Main get Value = " + Tools.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image_1at3n7ff5m5f1uuuj1n3ndj6d1g.png-45.4kB

如上所示,虽然数据隔离性方面没有问题,但是每一次设置值的时候都要进行非空判定是一件令人烦恼的事情,那么我们要如何解决呢?

解决 get() 返回 null 问题

创建名称为 ThreadLcoal4 的项目,继承 ThreadLocal 类产生的 ThreadLocalExt 类
ThreadLocalExt 代码

public class ThreadLocalExt extends ThreadLocal {
    public static ThreadLocal<Date> t1 = new ThreadLocal<Date>();

    protected Object initialValue() {
        return "我是默认值,第一次 get 的时候不再为空";
    }
}

main 线程代码

public class mainThread {
    public static ThreadLocalExt t1 = new ThreadLocalExt();
    public static void main(String[] args) {
        if(t1.get() == null){
            System.out.println("从未放过值");
        }
        System.out.println(t1.get());
        System.out.println(t1.get());
    }
}

image_1at3o62mgu42s701ob78qevl9.png-10.6kB

再次验证线程变量的隔离性

ThreadLocalExt

public class ThreadLocalExt extends ThreadLocal {
    public static ThreadLocal<Date> t1 = new ThreadLocal<Date>();

    protected Object initialValue() {
        return new Date().getTime();
    }
}

Tools

public class ThreadLocalExt extends ThreadLocal {
    public static ThreadLocal<Date> t1 = new ThreadLocal<Date>();

    protected Object initialValue() {
        return new Date().getTime();
    }
}

ThreadA

public class ThreadA extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10000; i++) {
                Tools.t1.set(new Date());
                System.out.println("Thread A get Value " + Tools.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ThreadB

public class ThreadB extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10000; i++) {
                Tools.t1.set(new Date());
                System.out.println("Thread B get Value " + Tools.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

mainThread

public class mainThread {
    public static void main(String[] args) {
        try {
            Thread A = new ThreadA();
            Thread B = new ThreadB();
            A.start();
            B.start();
            for (int i = 0; i < 10000; i++) {
                Tools.t1.set(new Date());
                System.out.println("Main get Value = " + Tools.t1.get());
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image_1at3oohpscoc168cnt1hq91cdc13.png-69.8kB

ThreadLocal能够存储数据,其作用域是线程,可通过以下几种方式使用: - **基本使用及原理**:每个ThreadLocal会创建一个ThreadLocalMap,以线程作为ThreadLocalMap的key,要存储的局部变量作为value,以此实现各个线程的局部变量隔离 [^2]。 - **结合业务对象使用**:将ThreadLocal放在业务对象里可体现高内聚,能让每一个线程都有一个独立的业务对象。使用时,通过特定方法如`ThreadLocalDemo.getInstance()`就能得到当前线程所需的值。例如以下代码: ```java package com.yanghs.test.traditional; /** * @author yanghs * @Description: * @date 2018/3/31 16:24 */ public class ThreadLocalTest { public static void main(String[] args) { for(int i=0; i<2;i++){ new Thread(new Runnable() { @Override public void run() { Double d = Math.random()*10; ThreadLocalDemo.getThreadInstance().setName("name"+d); new A().get(); new B().get(); } }).start(); } } static class A{ public void get(){ System.out.println(ThreadLocalDemo.getThreadInstance().getName()); } } static class B{ public void get(){ System.out.println(ThreadLocalDemo.getThreadInstance().getName()); } } } ``` Struts2的ActionContext就是使用这种方式绑定数据 [^3]。 - **应对特定需求的使用**:当需求是使用最高效的方式实现1000个时间的格式化时,可使用ThreadLocal创建线程级别变量,如创建10个SimpleDateFormat对象。不过具体使用该方案还是使用加锁方案,取决于线程及变量的复用率 [^4]。 - **避免内存泄漏的使用**:在线程池场景中,由于线程会重复使用,若忘记调用`remove()`,会导致ThreadLocal对象无法被回收。因此,要在`finally`块中调用`remove()`,示例代码如下: ```java try { // 使用 ThreadLocal } finally { threadLocal.remove(); } ``` 同时,使用`InheritableThreadLocal`(父子线程继承变量)时需谨慎 [^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值