Administrator
发布于 2023-01-28 / 57 阅读
0
0

ThreadLocal Best Practise

局部变量是线程安全的

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需要注意的有两点:

  1. ThreadLocal实例本身是不存储值,它只是提供了一个在当前线程中找到副本值得key。
  2. 是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
}

image-20221012090214675

结合上图,我们可以看到:

首先,业务代码中,有一个强引用,即: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
}

image-20221012090214675

结合上图,我们可以看到:

首先,业务代码中,有一个强引用,即: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

疑问

image-20210916105103348

今天,遇到一个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/**");
    }
}


评论