java并发编程 ThreadLocal

一、使用

  1.1 不用TheadLocal会有什么问题?

    多线程访问共享变量时,由于线程不安全问题,导致num得到的结果是无法确定的。此时如果变量是私有的,就用ThreadLocal,如果是共享的就要加锁。

public class ThreadLocalTest01 {
    public static Integer num = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                num += 5;
                System.out.println("num = " + num);
            }).start();
        }
    }
}

  

 

 

  1.2 使用ThreadLocal

    使得num是线程私有的,跟其他线程隔离

public class ThreadLocalTest02 {
    public static ThreadLocal<Integer> num = ThreadLocal.withInitial(() -> 0);

    public static void main(String[] args) {
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(() -> {
                Integer localNum = num.get();
                localNum += 5;
                num.set(localNum);
                System.out.println(Thread.currentThread().getName() + ", num = " + localNum);
            }, "thread-" + i);
        }
        for (Thread thread : threads) {
            thread.start();
        }
    }
}

 

 

 

二、要搞清以下问题

2.1 每个线程的变量副本 是如何存储的?

 

   数组有hash冲突,没有使用链表/红黑树解决,而是用黄金分割(斐波那契散列),能均衡分配到16个槽位上,超过16就扩容

  hashmap为什么不用,因为map是用来做存储的

/**
 * @author
 * @Description 结果一定是0~15,不重复
 * @date 2022/3/2
 */
public class Demo {
    private static final int HASH_INCREMENT = 0x61c88647;

    public static void magicHash(int size) {
        int hashCode = 0;
        for (int i = 0; i < size; i++) {
            hashCode = i * HASH_INCREMENT + HASH_INCREMENT;
            System.out.print((hashCode & (size - 1)) + ",");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        magicHash(16);
    }
}

 

2.2 是ThreadLocal怎么初始化的

 

2.3 使用场景那也是相当的丰富:
  1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  2、线程间数据隔离
  3、进行事务操作,用于存储线程事务信息。
  4、数据库连接,Session会话管理。
作用:
  因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
  既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题

四、ThreadLocal其他几个注意的点

只要是介绍ThreadLocal的文章都会帮大家认识一个点,那就是内存泄漏问题。我们先来看下面这张图。

 

 

/**
 * -Xms20M -Xmx20M -Xmn10M
 */
public class ThreadLocalOOM {
    public static void main(String[] args) {
        System.out.println("main 线程开始...");
        for (int i = 0; i < 100; i++) {
            ThreadLocal<Byte[]> threadLocal = new ThreadLocal<>();
            //  在当前线程存入一个value
            System.out.println("在线程:"+Thread.currentThread().getName()+" 添加一个threadLocal");
            threadLocal.set(new Byte[1024*1024]);
            // 方法解决,将threadLocal remove
            threadLocal.remove();
        }
        System.out.println("main 线程完美运行结束");
    }
}

 

上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。

1、Thread中有一个map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

posted @ 2022-03-02 09:28  星星之火可以燎源  阅读(20)  评论(0编辑  收藏  举报