ThreadLocal及扩展Local的使用

一.ThreadLocal的使用

1.1说明

a.ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。

b.ThreadLocal本身不存放数据,而是使用线程中的threadLocals属性来存储每个线程的变量副本。

c.使用线程池时,需要及时清理线程中的ThreadLocal,避免造成污染

d.ThreadLocal在创建子线程时不会把线程本地变量拷贝到子线程中,这就会导致子线程无法获取到本地线程保存的线程信息

1.2声明方式

a.ThreadLocal<Integer> NUM = new ThreadLocal<>();

线程首次使用前,需要先进行调用set方法进行复制,get方法默认返回为null

b.ThreadLocal<Integer> NUM = ThreadLocal.withInitial(()->0)

实现类为SuppliedThreadLocal,声明时进行处理化

 

1.3简单使用

static ThreadLocal<Integer> NUM = ThreadLocal.withInitial(()->0);
public static void test1(){
    new Thread(() -> {
        NUM.set(1);
        System.out.println("threadName:"+Thread.currentThread().getName()+" NUM:"+NUM.get());
    }).start();
    new Thread(() -> {
        NUM.set(1);
        System.out.println("threadName:"+Thread.currentThread().getName()+" NUM:"+NUM.get());
    }).start();
}

 每个线程都拥有自己的变量副本,线程之间的变量副本互不影响,从而避免了多线程操作共享资源造成的数据不一致问题。

1.4线程池使用

static ThreadLocal<Integer> NUM = ThreadLocal.withInitial(()->0);
    public static void test2(){
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,2,10L,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(3000));
        for(int i = 0 ; i < 6; i++){
            poolExecutor.execute(() -> {
                if (NUM.get() == null){
                    NUM.set(0);
                }
                NUM.set(NUM.get()+1);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:"+Thread.currentThread().getName()+";NUM:"+NUM.get());
                //移除
                NUM.remove();
            });
        }
        poolExecutor.shutdown();
    }

1.5类关系

线程(Thread)的threadLocals属性来存储每个线程的变量副本。threadLocals属性对应的是一个ThreadLocalMap的静态内部类对象,该对象内部维护了一个Entry数组来存储键值对(其中键是ThreadLocal的引用,值是线程变量的副本)。

Entry使用弱引用指向ThreadLocal的值,预防释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了,从而导致内存泄露的风险。

二.InheritableThreadLocal的使用

2.1说明

a.InheritableThreadLocal可以方便地让子线程自动获取父线程InheritableThreadLocal的数据。

b. 不能使用withInitial方法进行声明初始化

c.使用线程池时,线程不会每次都创建,也就不会进行父子线程之间的数据传递;如果使用池化的线程,只会在首次进行数据传递。

2.2简单使用

static InheritableThreadLocal<Integer> NUM = new InheritableThreadLocal<>();
    public static void test1(){
        Thread thread1 = new Thread(()->{
            NUM.set(NUM.get()+1);
            System.out.println("线程:"+Thread.currentThread().getName()+";NUM:"+NUM.get());
            Thread thread3 = new Thread(()->{
                NUM.set(NUM.get()+1);
                System.out.println("线程:"+Thread.currentThread().getName()+";NUM:"+NUM.get());
                //移除
                NUM.remove();
            },"thread3");
            thread3.start();
            //移除
            NUM.remove();
        },"thread1");
        thread1.start();
        Thread thread2 = new Thread(()->{
            NUM.set(NUM.get()+1);
            System.out.println("线程:"+Thread.currentThread().getName()+";NUM:"+NUM.get());
            //移除
            NUM.remove();
        },"thread2");
        thread2.start();
    }
    public static void main(String[] args) {
        NUM.set(1);
        test1();
    }

InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,方便必要信息的进一步传递。

2.3线程池使用

public static void test2(){
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,2,10L,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(3000));
        for(int i = 0 ; i < 10 ; i++){
            poolExecutor.execute(() -> {
                NUM.set(NUM.get()+1);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:"+Thread.currentThread().getName()+";NUM:"+NUM.get());
                //移除
                NUM.remove();
            });
        }
        poolExecutor.shutdown();
    }

只有线程首次使用时会继承父线程的值,线程移除InheritableThreadLocal时,后续使用无法继续使用父线程的值

2.4实现说明

2.4.1InheritableThreadLocal重写

InheritableThreadLocal重写了getMap和createMap方法,关联Thread中的ThreadLocal.ThreadLocalMap inheritableThreadLocals属性

2.4.2线程初始化

 

线程在初始化时会判断父线程的inheritableThreadLocals属性是否为空,不为空时会继承父线程的inheritableThreadLocals属性

  • 27
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocalJava中的一个线程级别的变量,它提供了一种简单的方式来在多线程环境中维护变量的值。每个线程都拥有自己独立的ThreadLocal实例,并且可以通过该实例来获取和设置其对应的变量的值。 ThreadLocal的原理是通过在每个线程中创建一个独立的副本来存储变量的值。这样,每个线程都可以独立地访问和修改自己的副本,而不会对其他线程产生影响。 ThreadLocal使用非常简单。首先,我们需要创建一个ThreadLocal对象,并指定要存储的变量类型。然后,我们可以通过调用ThreadLocal的get方法来获取当前线程中与该ThreadLocal对象关联的变量值,如果当前线程还没有设置过该变量,get方法会返回null。类似地,我们可以通过调用ThreadLocal的set方法来设置当前线程中与该ThreadLocal对象关联的变量值。 下面是一个简单的示例代码: ``` public class ThreadLocalExample { private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>(); public static void main(String[] args) { THREAD_LOCAL.set("Hello, world!"); Thread thread1 = new Thread(() -> { THREAD_LOCAL.set("Hello from thread 1!"); System.out.println(THREAD_LOCAL.get()); }); Thread thread2 = new Thread(() -> { THREAD_LOCAL.set("Hello from thread 2!"); System.out.println(THREAD_LOCAL.get()); }); thread1.start(); thread2.start(); System.out.println(THREAD_LOCAL.get()); } } ``` 在上面的示例中,我们创建了一个ThreadLocal对象`THREAD_LOCAL`用于存储String类型的变量。首先我们通过调用`THREAD_LOCAL.set("Hello, world!")`方法在主线程中设置了变量的值。然后,我们创建了两个新的线程并分别在其中设置了不同的值,并打印出来。最后,在主线程中我们也打印了变量的值。 运行上面的代码,你会看到输出结果类似于: ``` Hello from thread 1! Hello from thread 2! Hello, world! ``` 可以看到,每个线程都可以独立地访问和修改自己的变量副本,不会对其他线程产生影响。这样就确保了在多线程环境中,每个线程都可以维护自己的变量状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值