java-ThreadLocal

ThreadLocal

1、ThreadLocal简介

ThreadLocal用来创建线程局部变量。线程局部变量是每个线程都有自己独立的一个变量副本,而这个副本对其他线程是不可见的。这意味着每一个线程都可以在不影响其他线程的情况下,修改自己线程的副本值。ThreadLocal的实例通常是在类中以静态字段的方式存在的。

ThreadLocal的主要用途是解决多线程环境下的数据同步问题,使得每个线程都拥有独立的数据副本。因此,ThreadLocal通常用于在一个线程中保存信息,而在其他线程中不需要或者不应该访问这些信息。

public class Example {  
    // 创建一个线程局部变量  
    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();  
  
    public static void main(String[] args) {  
        // 两个线程同时运行  
        Runnable task1 = () -> {  
            threadLocal.set(100); // 为每个线程设置一个值  
            try {  
                Thread.sleep(2000); // 让线程睡眠一段时间,这样我们可以看到每个线程独立地保留了自己的值  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println(Thread.currentThread().getName() + " " + threadLocal.get()); // 打印当前线程的名字和其对应的值  
        };  
  
        Runnable task2 = () -> {  
            threadLocal.set(200); // 为每个线程设置一个值  
            try {  
                Thread.sleep(1000); // 让线程睡眠一段时间,这样我们可以看到每个线程独立地保留了自己的值  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println(Thread.currentThread().getName() + " " + threadLocal.get()); // 打印当前线程的名字和其对应的值  
        };  
  
        new Thread(task1, "Thread-1").start();  
        new Thread(task2, "Thread-2").start();  
    }  
}

在这个例子中,我们有两个线程,每个线程都设置了threadLocal的值,然后在稍后的时间打印了这个值。尽管两个线程访问的是相同的ThreadLocal实例,但是因为ThreadLocal使得每个线程都有自己的独立副本,所以它们打印出来的值是各自设置的值,而不是另一个线程的值。

2、ThreadLocal中的弱引用

对于弱引用类,当他没有被引用的时候,jvm的垃圾回收总会回收弱引用的对象。

ThreadLocal变量中的值是存储在每个线程的ThreadLocalMap中的,而这个Map中的键是ThreadLocal的弱引用。这意味着,如果ThreadLocal变量没有其他强引用指向它,那么它的值就可能会被垃圾回收器回收,尽管这个值还在被线程使用。这可能导致线程在后续访问这个值时,发现这个值已经被回收,从而引发错误。

为了避免这个问题,一种方法是确保ThreadLocal变量始终有强引用指向它。这可以通过在类中定义一个静态的ThreadLocal变量来实现。由于静态变量在类被加载时就被初始化,并且始终有强引用指向它,因此它的值不会被垃圾回收器回收。

这点上边我们已经说过了,但是使用静态的ThreadLocal还会造成另外一个问题,当线程的ThreadLocal类随gc而被销毁时,代表key的ThreadLocal被销毁了,但是他们的值还是被静态的ThreadLocal所引用,而不会被回收,如果值过多,或导致内存溢出,所以应该在线程结束的时候,调用threadLocal.remove() 方法来移除线程中ThreadLocal中的值。
示例代码:

public class Example {  
    // 创建一个静态的线程局部变量  
    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();  
  
    public static void main(String[] args) {  
        // 两个线程同时运行  
        Runnable task1 = () -> {  
            threadLocal.set(100); // 为每个线程设置一个值  
            try {  
                Thread.sleep(2000); // 让线程睡眠一段时间,这样我们可以看到每个线程独立地保留了自己的值  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println(Thread.currentThread().getName() + " " + threadLocal.get()); // 打印当前线程的名字和其对应的值  
            // 线程最后移除threadLocal中的值,移除的是当前线程的
            threadLocal.remove() 
        };  
  
        Runnable task2 = () -> {  
            threadLocal.set(200); // 为每个线程设置一个值  
            try {  
                Thread.sleep(1000); // 让线程睡眠一段时间,这样我们可以看到每个线程独立地保留了自己的值  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println(Thread.currentThread().getName() + " " + threadLocal.get()); // 打印当前线程的名字和其对应的值  
            // 线程最后移除threadLocal中的值,移除的是当前线程的
            threadLocal.remove() 
        };  
  
        new Thread(task1, "Thread-1").start();  
        new Thread(task2, "Thread-2").start();  
    }  
}

在这个示例中,我们定义了一个静态的ThreadLocal变量,并在两个线程中设置了它的值。由于这个ThreadLocal变量是静态的,因此它始终有强引用指向它,它的值不会被垃圾回收器回收,每个线程都可以保留自己的值。

3、ThreadLocal类的使用场景

ThreadLocal类在Java中主要被用于创建线程局部变量。这对于需要保证每个线程都有自己独立的数据副本,而同时保证数据在各个线程间不会共享的情况非常有用。以下是一些在生产环境中可能会使用ThreadLocal的情况:

  1. 数据库连接:在Web应用服务器中,往往使用ThreadLocal来为每个线程存储一个数据库连接,这样每个线程都可以使用自己的数据库连接,而不会影响其他线程。
  2. 事务管理:在一些复杂的应用中,你可能需要在一个线程中管理多个数据库事务。通过ThreadLocal,你可以为每个线程存储事务的信息,防止事务间的互相干扰。
  3. 会话管理:在Web应用中,你可能需要跟踪每个用户的会话状态。通过将会话信息存储在ThreadLocal中,可以确保每个线程的会话状态都是独立的。
  4. 线程安全的随机数生成:Random类不是线程安全的,如果在多个线程中同时使用Random类,可能会产生竞争条件。通过在ThreadLocal中存储一个Random实例,可以为每个线程提供一个独立的随机数生成器。
  5. 线程安全的日期格式化:SimpleDateFormat类也不是线程安全的,如果在多个线程中同时使用SimpleDateFormat类,可能会产生竞争条件。通过在ThreadLocal中存储一个SimpleDateFormat实例,可以为每个线程提供一个独立的日期格式化工具。
  6. 缓存:在一些需要缓存数据的场景中,ThreadLocal可以被用来为每个线程提供一个独立的缓存,避免线程间的数据共享和污染。

4、InheritableThreadLocal

子线程继承当前父线程中存在的值。

当前父线程存在啥值,子线程就继承啥。对于同一个对象,父线程修改后,子线程可以感知到该对象的变化。子线程运行后,父线程再添加新值,子线程不会再感知

public class ExampleInheriterableThreadLocal {
    
    private static BrokerOfThread broker = new BrokerOfThread("broker-init");
    private static InheritableThreadLocal<BrokerOfThread> threadLocal = new InheritableThreadLocal<>();

    static {
        threadLocal.set(broker);
    }

    public static void main(String[] args) throws InterruptedException {

        final Thread threadA = new Thread() {
            @Override
            public void run() {
                final BrokerOfThread brokerOfThread = threadLocal.get();

                while (brokerOfThread.isRun()) {
                    final BrokerOfThread brokerOfThread1 = threadLocal.get();
                    System.out.println("当前线程是" + Thread.currentThread().getName() + "----正在运行,当前的threadLocal值是-->" + brokerOfThread1.toString());
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println(Thread.currentThread().getName() + "-----线程退出");

            }
        };

        final Thread threadB = new Thread() {
            @Override
            public void run() {
                final BrokerOfThread brokerOfThread = threadLocal.get();

                while (brokerOfThread.isRun()) {
                    final BrokerOfThread brokerOfThread1 = threadLocal.get();
                    System.out.println("当前线程是" + Thread.currentThread().getName() + "----正在运行,当前的threadLocal值是-->" + brokerOfThread1.toString());
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println(Thread.currentThread().getName() + "-----线程退出");

            }
        };

        threadA.start();
        threadB.start();

        Thread.sleep(1000);

        threadLocal.set(new BrokerOfThread("broker-after"));
        System.out.println("main线程为InheritableThreadLocal设置了新的值");

        Thread.sleep(10000);
        broker.setRun(false);

        Thread.sleep(10000);

    }

}


class BrokerOfThread {

    private String name;
    private long timestamp;
    private boolean run;

    public BrokerOfThread(String name) {
        this.name = name;
        this.run = true;
        this.timestamp = System.currentTimeMillis();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public boolean isRun() {
        return run;
    }

    public void setRun(boolean run) {
        this.run = run;
    }

    @Override
    public String toString() {
        return "BrokerOfThread{" +
                "name='" + name + '\'' +
                ", timestamp=" + timestamp +
                ", run=" + run +
                '}';
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值