局部变量是线程安全的
public class SephoneTest {
static ExecutorService executor = Executors.newFixedThreadPool(3);
public static void main(String[] args) {
SephoneTest sephoneTest = new SephoneTest();
executor.execute(() -> {
sephoneTest.test01();
});
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
executor.execute(() -> {
sephoneTest.test01();
});
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
executor.execute(() -> {
sephoneTest.test01();
});
}
public void test01() {
int a = 10;
a++;
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("a:"+a);
}
}
输出:
a:11
a:11
a:11
根据上面的输出,我们知道,局部变量是不共享的,只有成员变量才是共享的。因此,局部变量线程安全的,成员变量是线程不安全的。
但是需要注意的是,如果将局部变量赋值为 引用类型变量,那么A线程修改了这个引用类型变量对应的值,B线程中的局部变量 也会发生改变。这个是因为,其实他们指向的是同一个 内存地址。如下:
package com.wp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.wp.list.Person;
public class SephoneTest {
static ExecutorService executor = Executors.newFixedThreadPool(3);
private volatile Person person = new Person();
public static void main(String[] args) {
SephoneTest sephoneTest = new SephoneTest();
executor.execute(() -> {
sephoneTest.test01();
});
executor.execute(() -> {
sephoneTest.test02();
});
executor.execute(() -> {
sephoneTest.test03();
});
}
public void test01() {
Person p = person;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
p.setName(p.getName()+"1");
System.out.println("p:"+p.getName());
}
public void test02() {
Person p = person;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
p.setName(p.getName()+"1");
System.out.println("p:"+p.getName());
}
public void test03() {
Person p = person;
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
p.setName(p.getName()+"1");
System.out.println("p:"+p.getName());
}
}
输出:
p:null1
p:null11
p:null111
ThreadLocal初步使用
正是因为,局部变量 指向 引用类型的时候,也会出现线程不安全的情况,因此,为了解决这个问题,这里提出了ThreadLocal
package com.wp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.wp.list.Person;
public class SephoneTest {
static ExecutorService executor = Executors.newFixedThreadPool(3);
private volatile ThreadLocal<Person> tp = new ThreadLocal() {
@Override
protected Object initialValue() {
// TODO Auto-generated method stub
return new Person();
}
};
public static void main(String[] args) {
SephoneTest sephoneTest = new SephoneTest();
executor.execute(() -> {
sephoneTest.test01();
});
executor.execute(() -> {
sephoneTest.test02();
});
executor.execute(() -> {
sephoneTest.test03();
});
}
public void test01() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Person person = tp.get();
person.setName(person.getName()+"1");
tp.set(person);
System.out.println("p:"+tp.get().getName());
}
public void test02() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Person person = tp.get();
person.setName(person.getName()+"1");
tp.set(person);
System.out.println("p:"+tp.get().getName());
}
public void test03() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Person person = tp.get();
person.setName(person.getName()+"1");
tp.set(person);
System.out.println("p:"+tp.get().getName());
}
}
输出:
p:null1
p:null1
p:null1
根据输出,可以看到,这里已经解决了 引用类型数据 的 线程安全问题。
分析
参考;http://www.iocoder.cn/JUC/sike/ThreadLocal/?vip
ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,
而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。ThreadLocal
实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
可以说ThreadLocal为多线程环境下变量问题提供了另外一种解决思路。
也就是说,其实ThreadLocal就类似于 局部变量。我们知道,方法内定义的变量,叫做局部变量,而局部变量是不共享的,只有成员变量才是共享的。而ThreadLocal就可以看做是一个 局部变量,是不共享的,他有多个副本,每个线程对应其中的一个副本
ThreadLocal定义了四个方法:
- get():返回此线程局部变量的当前线程副本中的值。
- initialValue():返回此线程局部变量的当前线程的“初始值”。
- remove():移除此线程局部变量当前线程的值。
- set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
除了这四个方法,ThreadLocal内部还有一个静态内部类ThreadLocalMap,该内部类才是实现线程隔离机制的关键,get()、set()、remove()都是基于该内部类操作。ThreadLocalMap提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前ThreadLocal对象,value则是对应线程的变量副本。
对于ThreadLocal需要注意的有两点:
- ThreadLocal实例本身是不存储值,它只是提供了一个在当前线程中找到副本值得key。
- 是ThreadLocal包含在Thread中,而不是Thread包含在ThreadLocal中,有些小伙伴会弄错他们的关系。
/**
首先,Thread类中,包含一个成员变量ThreadLocalMap
*/
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
/**
其次:ThreadLocalMap其内部利用Entry来实现key-value的存储
entry中的key 是 ThreadLocal实例,value 就是对应的值
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
ThreadLocalMap 是怎么维护多个Entry的呢?
其实,很简单,ThreadLocalMap中定义了一个 Entry数组,然后,往这个数组加值的时候,首先,根据ThreadLocal实例的hashcode,确定在Entry数组中的位置,然后将这个新创建的entry设置到这个Entry数组中
*/
static class ThreadLocalMap {
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
}
我们基于以下知识,分析下get方法,进行加,巩固
public T get() {
Thread t = Thread.currentThread();
// 获取Thread类中 的ThreadLocalMap属性
ThreadLocalMap map = getMap(t);
if (map != null) {
// 根据ThreadLocal实例的hascode,获取到对应的entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// entry中的value就是 我们保存的值
T result = (T)e.value;
return result;
}
}
// 为什么我们需要重写initialValue方法呢?原因就在这里,如果ThreadLocalMap是个空,就会调用initialValue方法,进行初始化设置,并且返回 initialValue 的 返回值
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
结合threadLocal分析弱引用
下面,我们结合代码ThreadLocal,实际说说弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可以看出来,只有Key是弱引用类型的,Value并非弱引用。
为什么,要将ThreadLocal.Entry中的key,定义为弱引用呢?
我们正常使用threadLocal的格式如下:
private static ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
try {
threadLocal.set(new Session(1, "Misout的博客")); // 标记1
// 其它业务逻辑
} finally {
threadLocal = null; // 标记2
}
结合上图,我们可以看到:
首先,业务代码中,有一个强引用,即:ThreadLocal ref -----> ThreadLocal
然后,Heap堆内,有一个弱引用,即图中的弱引用虚线箭头
假设,这个时候,我们执行到finally里面,将threadLocal置为null。那么此时会发生什么事情呢?
首先,这个强引用断掉了,即ThreadLocal ref -----> ThreadLocal,这个强引用断掉了
接下来,因为Heap堆内,有一个弱引用,但是弱引用我们知道,当下一次GC开始工作后,一定会被清除,所以,下一次GC工作时,这个弱引用也断掉了
因为弱引用断掉了,所以Entry中的key,变成了null,但是value值没变
但是ThreadLocal中,包含了一个代码,就是,如果Entry的key为null,就会将这个Entry,从ThreadLocalMap中移除,也即ThreadLocalMap 对Entry的强引用,也断掉了,那么Entry就不可达了,所以最后,Entry会被当做垃圾回收掉。
Entry的key为null,就会将这个Entry,从ThreadLocalMap中移除,这个逻辑,是在哪里呢?
就在,ThreadLocal.ThreadLocalMap类的expungeStaleEntry方法中
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--; // 以上代码,将entry的value赋值为null,这样方便GC时将真正value占用的内存给释放出来;将entry赋值为null,size减1,这样这个slot就又可以重新存放新的entry了
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len); // 从staleSlot后一个index开始向后遍历,直到遇到为null的entry
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) { // 如果entry的key为null,则清除掉该entry
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) { // key的hash值不等于目前的index,说明该entry是因为有哈希冲突导致向后移动到当前index位置的
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null) // 对该entry,重新进行hash并解决冲突
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i; // 返回经过整理后的,位于staleSlot位置后的第一个为null的entry的index值
}
expungeStaleEntry方法不止清理了staleSlot位置上的entry,还把staleSlot之后的key为null的entry都清理了,并且顺带将一些有哈希冲突的entry给填充回可用的index中。
而这个expungeStaleEntry方法,在threadLocal的get set remove等多个方法中,都会被调用到。
接下来,我们再反过来看下,如果不将ThreadLocal.Entry中的key,定义为弱引用,会有什么问题?
还是用下面的代码,来说明
private static ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
try {
threadLocal.set(new Session(1, "Misout的博客")); // 标记1
// 其它业务逻辑
} finally {
threadLocal = null; // 标记2
}
结合上图,我们可以看到:
首先,业务代码中,有一个强引用,即:ThreadLocal ref -----> ThreadLocal
然后,Heap堆内的弱引用,即图中的弱引用虚线箭头,就是一个强引用了
假设,这个时候,我们执行到finally里面,将threadLocal置为null。那么此时会发生什么事情呢?
首先,这个强引用断掉了,即ThreadLocal ref -----> ThreadLocal,这个强引用断掉了
接下来,因为Heap堆内,还有一个强引用,就是Entry 指向的threadLocal这个引用
因为这个是强引用,所以,GC不会将threadLocal对象当做垃圾,进行回收。因此,Entry 的key和value都不是null。
而expungeStaleEntry方法,发现Entry的key不是null,那么他就不知道 是否应该将这个entry从Map中 移除。
这样,就会导致threadLocalMap,引用的对象,越来越多,占的内存越来越大,最后OOM
这也是,threadLocal使用不当,导致内存泄漏的原因之一。
expungeStaleEntry就是最核心的清理方法,这也是ThreadLocalMap的一种防范机制,因为ThreadLocalMap的生命周期和线程是一样长的,不采取这种防范机制,是会造成内存泄漏的。
如果多定义了几个ThreadLocal对象,并且线程都将占用内存比较大的对象给放到对应的线程中,可能就会造成OOM异常了。
如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露
最佳实践remove
1.每次使用完ThreadLocal,都调用它的remove()方法,清除数据,而不是将threadLocal对象置为null。
因为,调用remove方法,会显示的,将Entry,从threadLocalMap 中移除掉。
上面内存泄漏,其实最主要,是因为threadLocalMap 对Entry的引用,一直存在着。
那么如果将threadLocalMap 对Entry的引用,断掉后,entry就不可达了,那么就不会出现 内存泄漏了。
那么怎么让这个引用断掉呢?就是,我们要说的remove方法了
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
找 到key对应的 entry,然后将这个entry从ThreadLocalMap移除,这样entry对应就没有人引用了,会被自动GC回收
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear(); // 这一步,会将entry中threadLocal的引用,显示的断掉
expungeStaleEntry(i);
return;
}
}
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--; // 以上代码,将entry的value赋值为null,这样方便GC时将真正value占用的内存给释放出来;将entry赋值为null,size减1,这样这个slot就又可以重新存放新的entry了
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len); // 从staleSlot后一个index开始向后遍历,直到遇到为null的entry
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) { // 如果entry的key为null,则清除掉该entry
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) { // key的hash值不等于目前的index,说明该entry是因为有哈希冲突导致向后移动到当前index位置的
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null) // 对该entry,重新进行hash并解决冲突
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i; // 返回经过整理后的,位于staleSlot位置后的第一个为null的entry的index值
}
2.在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。
对于没有使用线程池的方法来说,因为每次线程运行完就退出了,Map里面引用的所有对象都会被垃圾回收,所以没有关系。
可是,在实际使用中我们都是会用线程池去维护我们的线程,比如在Executors.newFixedThreadPool()时创建线程的时候,为了复用线程是不会结束的,所以threadLocal内存泄漏就值得我们关注。
类似于这种:
threadLocal.set(...);
try {
...
} finally {
threadLocal.remove();
}
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executor.execute(() -> {
try {
long threadId = Thread.currentThread().getId();
int before = getValue();
increment();
int after = getValue();
System.out.println("threadId: " + threadId + ", before: " + before + ", after: " + after);
} finally {
// 清理线程本地存储
remove();
}
});
}
executor.shutdown();
}
为何通常“将ThreadLocal变量设置为static”?
Java static:
在Java语言中,static表示“静态”的意思,使用场景可以用来修饰成员变量和成员方法,当然也可以是静态代码块。static的主要作用在于创建独立于具体对象的域变量或者方法。这里我们不对static展开累述,只要讲明白static变量即可。static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本【存放在方法区】,它当且仅当在类初次加载时会被初始化【加final和不加final的static变量初始化的位置不一样】。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。也就是说,在一个线程内,没有被static修饰的ThreadLocal变量实例,会随着所在的类多次创建而被多次实例化,虽然ThreadLocal限制了变量的作用域,但这样频繁的创建变量实例是没有必要的。
ThreadLocal变量为什么用static修饰
答案是,为了避免重复创建TSO(thread specific object,即与线程相关的变量。),我们知道,一个ThreadLocal实例对应当前线程中的一个TSO实例。因此,如果把ThreadLocal声明为某个类的实例变量(而不是静态变量),那么每创建一个该类的实例就会导致一个新的TSO实例被创建。显然,这些被创建的TSO实例是同一个类的实例。于是,同一个线程可能会访问到同一个TSO(指类)的不同实例,这即便不会导致错误,也会导致浪费(重复创建等同的对象)!因此,一般我们将ThreadLocal使用static修饰即可。
理由:
为了避免重复创建TSO(thread specific object,即与线程相关的变量。)
需要注意的是:无法解决共享对象的更新问题。(引用于《阿里巴巴JAVA开发规范》)我的理解是,static定义的类变量本来是可以进行变量共享的,但是因为ThreadLocal根除了对变量的共享,所以static Thread< xxx> object无法实现类的共享和同步更新。
分析
我们知道,一个ThreadLocal实例对应当前线程中的一个TSO实例。因此,如果把ThreadLocal声明为某个类的实例变量(而不是静态变量),那么每创建一个该类的实例就会导致一个新的TSO实例被创建。显然,这些被创建的TSO实例是同一个类的实例。于是,同一个线程可能会访问到同一个TSO(指类)的不同实例,这即便不会导致错误,也会导致浪费(重复创建等同的对象)!因此,一般我们将ThreadLocal使用static修饰即可。
由于ThreadLocal是某个类的一个静态变量。因此,只要相应的类没有被垃圾回收掉,那么这个类就会持有对相应ThreadLocal实例的引用。
结论:
1 static 防止无意义多实例
2 当static时,ThreadLocal ref生命延长-ThreadMap的key在线程生命期内始终有值-ThreadMap的value在线程生命期内不释放——故线程池下,static修饰TrheadLocal引用,必须(1)remove 或(2)手动 ThreadLocal ref = null
疑问
今天,遇到一个threadLocal的问题,不太能理解,都说threadLocal,作为Entry的key,会变成成弱引用,所以GC的时候,会被回收。
但是,我们在代码中,已经使用static定义了一个变量,来指向threadLocal对象,这个应该是强引用啊,为什么还会被回收呢?
答案:
如果手动调用 threadLocal.remove()方法,那么在remove方法中,会手动将这个testThreadLocal置为null,这样,这个强引用就断掉了。
实现代码如下的:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
找 到key对应的 entry,然后将这个entry从ThreadLocalMap移除,这样entry对应就没有人引用了,会被自动GC回收
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear(); // 这一步,会将entry中threadLocal的引用,显示的断掉
expungeStaleEntry(i);
return;
}
}
}
public void clear() {
this.referent = null;
}
上面的e.clear中,就是将这个testThreadLocal置为null,这样,这个强引用就断掉了。
工作中使用
package com.yum.ec3.promotion.thread;
import com.yum.ec3.promotion.bo.enums.JoinFlagEnum;
import com.yum.ec3.promotion.exception.BusinessLogicException;
import com.yum.ec3.promotion.exception.PromoExceptionConstants;
import com.yum.ec3.promotion.constant.ThreadConstants;
import com.yum.ec3.promotion.bo.enums.TenantEnum;
import org.apache.commons.collections.MapUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* 优惠的threadLocal
*
* 慎用!慎用!慎用!重要的事情说3遍
*/
public class PromoThreadLocal {
// 优惠服务线程变量
private static ThreadLocal<Map<String,Object>> threadLocalMap = ThreadLocal.withInitial(() -> new HashMap<>(5));
public static void setTenant(TenantEnum tenant) {
Map<String, Object> objectMap = threadLocalMap.get();
// 利用饿汉单例模式占用对象,不让GC对ThreadLocal进行垃圾回收
if (MapUtils.isEmpty(objectMap)) {
objectMap = new HashMap<>(5);
}
objectMap.put(ThreadConstants.THREAD_TENANT, tenant);
threadLocalMap.set(objectMap);
}
/**
*
*
* 设置当比订单使用的原始券码
* @param originCouponCode
*/
public static void setOriginCouponCode(String originCouponCode) {
Map<String, Object> objectMap = threadLocalMap.get();
// 利用饿汉单例模式占用对象,不让GC对ThreadLocal进行垃圾回收
if (MapUtils.isEmpty(objectMap)) {
objectMap = new HashMap<>(5);
}
objectMap.put(ThreadConstants.ORIGIN_COUPON_CODE, originCouponCode);
threadLocalMap.set(objectMap);
}
/**
*
* JoinFlagEnum
* 设置当比订单使用的原始券码
* @param joinFlagEnum
*/
public static void setJoinFlagEnum(JoinFlagEnum joinFlagEnum) {
Map<String, Object> objectMap = threadLocalMap.get();
// 利用饿汉单例模式占用对象,不让GC对ThreadLocal进行垃圾回收
if (MapUtils.isEmpty(objectMap)) {
objectMap = new HashMap<>(5);
}
objectMap.put(ThreadConstants.JOIN_MESSAGE, joinFlagEnum);
threadLocalMap.set(objectMap);
}
public static TenantEnum getTenant() {
Object tenant = Optional.ofNullable(threadLocalMap.get().get(ThreadConstants.THREAD_TENANT)).orElse(null);
if (Objects.isNull(tenant)) {
// 抛异常
throw new BusinessLogicException(PromoExceptionConstants.EXTERNAL_PARAMETER_ERROR);
}
if (tenant instanceof TenantEnum) {
TenantEnum tenantEnum = (TenantEnum) tenant;
return tenantEnum;
} else {
// 抛异常
throw new BusinessLogicException(PromoExceptionConstants.EXTERNAL_PARAMETER_ERROR);
}
}
/**
* 当比订单使用的原始券码
* @return
*/
public static String getOriginCouponCode() {
Object originCouponCode = Optional.ofNullable(threadLocalMap.get().get(ThreadConstants.ORIGIN_COUPON_CODE)).orElse(null);
// 利用饿汉单例模式占用对象,不让GC对ThreadLocal进行垃圾回收
if (Objects.isNull(originCouponCode)) {
// 抛异常
throw new BusinessLogicException(PromoExceptionConstants.EXTERNAL_PARAMETER_ERROR);
}
return String.valueOf (originCouponCode);
}
/**
* 获取当前拼单状态
* @return
*/
public static JoinFlagEnum getJoinFlagEnum() {
Object joinFlagObj = Optional.ofNullable(threadLocalMap.get().get(ThreadConstants.JOIN_MESSAGE)).orElse(null);
// 利用饿汉单例模式占用对象,不让GC对ThreadLocal进行垃圾回收
if (Objects.isNull(joinFlagObj)) {
// 抛异常
throw new BusinessLogicException(PromoExceptionConstants.EXTERNAL_PARAMETER_ERROR, "拼单订单类型缺失");
}
if (joinFlagObj instanceof JoinFlagEnum) {
JoinFlagEnum joinFlagEnum = (JoinFlagEnum) joinFlagObj;
return joinFlagEnum;
} else {
// 抛异常
throw new BusinessLogicException(PromoExceptionConstants.EXTERNAL_PARAMETER_ERROR, "拼单订单参数错误");
}
}
/**
* 线程变量清空
*/
public static void clear() {
threadLocalMap.get().clear();
}
}
package com.yum.ec3.promotion.interceptor;
import com.yum.ec3.promotion.thread.PromoThreadLocal;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class PromoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 清除threadLocal中的值
clearThreadLocal();
return true;
}
private void clearThreadLocal() {
PromoThreadLocal.clear();
}
}
package com.yum.ec3.promotion.config;
import com.yum.ec3.promotion.interceptor.PromoInterceptor;
import com.yum.ec3.promotion.util.MiscUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
@ComponentScan({"com.yum.ec3.promotion", "com.yumc.brand.promotion", "com.yum.ec3.misc.client", "com.yum.ec3.thirdparty"})
public class ServiceConfig implements WebMvcConfigurer {
@Bean
public MiscUtils miscUtils() {
return new MiscUtils();
}
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(new ApiInfoBuilder()
.title("Swagger2 - calculate.service")
.version("2.0.0")
.build()).select()
.apis(RequestHandlerSelectors.basePackage("com.yum.ec3"))
.paths(PathSelectors.any()).build();
}
@Bean
public PromoInterceptor promoInterceptor(){
return new PromoInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(promoInterceptor())
.addPathPatterns("/**")
// 排除swagger的过滤
.excludePathPatterns("/swagger-resources/**", "/swagger-ui.html/**");
}
}