package com.gch.test3;
/**
i = i + 1这一行代码在底层有几步操作?
答:在底层分成了三步操作。
*/
public class Test {
public static void main(String[] args) {
int i = 0;
i = i + 1;
System.out.println(i); // 1
}
}
二. ThreadLocal
ThreadLocal:线程隔离。
ThreadLocal可以实现每一个线程都有自己的专属本地变量。
隔离是一种需求,但是隔离的需求出现的本质并不是因为我要线程安全所以我要隔离,而是因为有些业务场景就是需要业务数据之间隔离,只不过隔离后恰好它线程安全。举例:比如你每个用户的请求,你是不是都要知道用户的信息,那你这个用户的信息存哪,是不是每个线程都要存一份。
- ThreadLocal是每个线程相互隔离的,ThreadLocal可以实现线程之间的数据隔离。
ThreadLocal的应用场景?
- ThreadLocal用来解决数据库连接、Session管理等。
ThreadLocal的作用:
1. 线程隔离: ThreadLocal提供了一种在多线程环境下实现线程数据的隔离的方式,使每 个线程都可以独立地的使用自己的专属本地变量,不受其它线程的干扰。
2. 线程上下文传递: ThreadLocal可以方便的在多个方法中传递线程相关的数据,而不需要 显式地通过参数传递。
ThreadLocal常用方法
ThreadLocal内存泄漏问题了解不?
- ThreadLocalMap 的 Key 为 ThreadLocal 的弱引用,而 Value 是强引用。所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,Key会被清理掉=>Key为null,而Value不会被清理掉,Value数据还存在,垃圾回收不掉。
- 这样一来,ThreadLocalMap中就会出现Key为null的Entry,假如我们不做任何措施的话,Value永远无法被GC回收,这个时候就可能会产生内存泄漏。
- ThreadLocalMap实现中已经考虑了这种情况,在使用完ThreadLocal方法后最好手动调用remove() 方法来清除ThreadLocal变量值,避免潜在地内存泄漏问题,特别是在线程池等长时间运行地环境中。
提问: 我们在定义ThreadLocal变量时,定义的是static全局变量,既然是全局变量为什么 在不同的线程中去进行get和set还能做到线程隔离呢? ****--- ThreadLocal实现线程隔离 的底层原理?
- ThreadLocal只是一个Key,每一个线程它是一个Thread对象,在Thread类中有一个私有字段/属性叫threadLocals,threadLocals它是一个ThreadLocalMap对象,用于存储线程的局部变量。每个线程都有一个ThreadLocalMap对象,ThreadLocalMap对象内部使用Entry数组来存储键值对,它的Key是ThreadLocal的引用,通过弱引用(WeakReference)来引用对应的ThreadLocal对象,这是为了避免内存泄漏的问题,因为 ThreadLocalMap 对 ThreadLocal 的引用是弱引用,当 ThreadLocal 对象没有其他强引用时,会被垃圾回收器回收。
- Value是具体的线程局部变量的值/对应的是线程变量副本,Value是强引用。
ThreadLocal它里面有一个静态内部类,叫做ThreadLocalMap
ThreadLocalMap 是 ThreadLocal 的静态内部类
- ThreadLocalMap通过当前线程对象Thread来查找对应的ThreadLocalMap对象,并在该对象中根据ThreadLocal对象作为Key来存储和获取相应的值。
- ThreadLocal本身是不存储值的, 它只是提供一个能查到这个值的Key给你。
- ThreadLocal是包含在Thread中,而不是Thread包含在ThreadLocal中。
ThreadLocal实现线程隔离的Demo:
package com.gch.concurrent;
/**
* ThreadLocal可以实现线程之间的数据隔离。
* ThreadLocal的作用:
* 1.线程隔离:ThreadLocal提供了一种在多线程环境下实现线程数据的隔离的方式,使每个线程都可以独立地的使用变量,不受其它线程的干扰。
* 2.线程上下文传递:ThreadLocal可以方便的在多个方法中传递线程相关的数据,而不需要显式地通过参数传递。
*/
public class ThreadLocalDemo {
public static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
System.out.println(threadLocal.get()); // null
threadLocal.set(0);
System.out.println(threadLocal.get()); // 0
},"A");
Thread t2 = new Thread(()->{
System.out.println(threadLocal.get()); // null
threadLocal.set(1);
System.out.println(threadLocal.get()); // 1
},"B");
t1.start(); // 开启A线程
t1.join(); // 插入线程/插队线程:哪个线程调用了join()方法,哪个线程就先执行完毕,再执行其它线程
t2.start(); // 开启B线程
System.out.println("main线程ID = " + Thread.currentThread().getId()); // 1
System.out.println(threadLocal.get()); // null
}
}
ThreadLocal在多个方法之间实现线程上下文数据的传递的Demo:
package com.gch.concurrent;
/**
通过ThreadLocal在多个方法之间传递相同线程上下文的数据,而不需要显式的通过参数传递
可以在复杂的业务逻辑中简化代码的编写和维护
*/
public class ThreadContextExample {
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
/**
设置线程上下文数据
*/
threadLocal.set("Initial Value");
// 在主线程中调用方法A
methodA();
threadLocal.remove(); // 清除线程上下文数据
}
public static void methodA(){
String value = threadLocal.get(); // 获取线程上下文数据
System.out.println("Value in methodA: " + value);
// 在方法A中调用方法B
methodB();
}
public static void methodB(){
String value = threadLocal.get(); // 获取线程上下文数据
System.out.println("Value in methodB: " + value);
// 在方法B中调用方法C
methodC();
}
public static void methodC(){
String value = threadLocal.get(); // 获取线程上下文数据
System.out.println("Value in methodC: " + value);
}
}