• 3 ThreadLocal-TransmittableThreadLocal


    TTL的原理

    为了解决InheritableThreadLocal无法处池内线程ThreadLocal传递问题。

    线程池ThreadLocal的特殊性

    池线程中的ThreadLocal,和父子线程间的ThreadLocal传递有一定的特殊性:

    1. 池内线程有自己的ThreadLocal。
      a. 线程池可以指定ThreadFactory,通过ThreadFactory创建的Thread,可以设置自己的ThreadLocal。
      b. 池内线程创建时,同样是业务线程触发,因此业务线程可以看做池线程的父线程,因此业务线程的InheritableThreadLocal,同样会被复制到池线程中。
    2. 不同任务的ThreadLocal存在不一致性。例如:业务线程A的CurrentUserThreadLocal存储的是用户甲,业务线程B的CurrentUserThreadLocal存储的事用户乙。

    ThreadLocal的传递时机

    线程池中线程的执行过程分析,唯一可以变动ThreadLocal的时机,只有run方法,因为run方法是业务唯一可以控制的代码。当然这也是一句废话,因为池内线程只处理run方法。

    因此大致逻辑如下:

    1. 提交业务线程时,备份该业务线程的ThreadLocal。
    2. 池线程执行时,备份池线程的ThreadLocal。
    3. 根据步骤一的备份,恢复备份数据到池线程中,完成业务ThreadLocal到池线程ThreadLocal的传递。
    4. 根据步骤二的备份,复原池线程的ThreadLocal。

    为什么备份业务线程的ThreadLocal?
    因为业务线程可能会消亡,消亡后,是无法获取ThreadLocal对应的值,因此要备份。

    为什么要备份池线程的ThreadLocal,执行完成后,还有复原,而不是直接移除掉。
    因为线程池的一个拒绝策略是:callerRun,当触发后,其实执行run方法的不再是池线程,而是业务线程本身,如果移除掉,就会导致业务线程后续无法再次获取。

    自定义TTL

    单一ThreadLocal传递

    只需要传递CurrentUser,那么可以自定义如下。

    public class CurrentUserRunnable implements Runnable {
        private String currentUser;
        private Runnable runnable;
    
        @Override
        public void run() {
            try {
                //设置池线程的currentUser
                CurrentUserUtil.set(currentUser);
                //真正的执行run方法
                runnable.run();
            } finally {
                //移除池线程的CurrentUser
                CurrentUserUtil.remove();
            }
        }
    
        public CurrentUserRunnable(Runnable runnable) {
            this.runnable = runnable;
            //备份 业务线程的CurrentUser
            currentUser = CurrentUserUtil.get();
        }
    
    }
    
    CurrentUserUtil就是对    
    private static ThreadLocal<String> currentUserTL = new ThreadLocal<>();
    做了包装,提供了静态get、set、remove方法,就不做展示。
    
    
    
    运行Demo
    public class CurrentUserDemo {
        public static void main(String[] args) {
            CurrentUserUtil.set("jkf");
            ExecutorService executorService = Executors.newFixedThreadPool(1);
            executorService.execute(new CurrentUserRunnable(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + CurrentUserUtil.get());
                }
            }));
        }
    }
    输出结果:
    pool-1-thread-1:jkf
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    当然上述也存在问题:run方法的finally移除了currentUser,如果触发calllerRun拒绝策略,就会导致业务线程再也无法获取CurrentUser。

    稍微的优化

    添加ExecutorThreadUtil,包装:
    
    private static ThreadLocal<Boolean> executorThreadTL= new ThreadLocal() {
            protected Boolean initialValue() {
                return false;
            }
        };
    
    
    
    修改CurrentUserDemo,创建executorService后,添加ThreadFactory
    public class CurrentUserDemo {
        public static void main(String[] args) {
           
            //设置创建ThreadFactory,表明当前线程为池线程。
            ((ThreadPoolExecutor) executorService).setThreadFactory(new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    //表明当前线程是池线程
                    ExecutorThreadUtil.set();
                    return t;
                }
            });
        }
    }
    
    修改CurrentUserRunnable的finall
    //当前线程是池线程
                if(ExecutorThreadUtil.isExecutorThread()){
                    //移除池线程的CurrentUser
                    CurrentUserUtil.remove();
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    通过ExecutorThreadUtil标明当前线程是否是池线程,当触发callerRun时,会给当前线程的ThreadLocalMap中,添加executorThreadTL,value=false,会稍微占用一些内存。

    缺点:上述方案只能针对特定的ThreadLocal,进行处理,例如:需要传递CurrentOrg,那么就需要有CurrentOrgRunnable;如果要传递CurrentUser和CurrentOrg,那么还需要特殊处理,当然上述情况可以提取一个ThreadLocalRunnable,把ThreadLocal数组作为入参。

    多ThreadLocal传递

    刚才说了单一ThreadLocal传递的方案,但是针对多ThreadLoal传递的情况,需要抽象一个ThreadLocalRunnable,具体优化如下:

    public class ThreadLocalRunnable implements Runnable {
    	//存储业务的ThreadLocal对应的值。
        private Map<ThreadLocal, Object> tls;
        private Runnable runnable;
    
        /**
         * 创建任务时,把需要的ThreadLocal全部传递进来
         */
        public ThreadLocalRunnable(Runnable run, List<ThreadLocal> allThreadLocal) {
            this.runnable = run;
            //备份业务的ThreadLocal
            backupThreadLocal(allThreadLocal);
        }
    
        @Override
        public void run() {
            try {
            	//恢复业务的ThreadLocal值到池线程中
                recoveryThreadLocal();
                //真正的执行run方法
                runnable.run();
            } finally {
            	//移除恢复的ThreadLocal
                removeThreadLocal();
            }
        }
    
        /**
         * 备份传递进来的ThreadLocal
         */
        private void backupThreadLocal(List<ThreadLocal> allThreadLocal) {
            if (!CollectionUtils.isEmpty(allThreadLocal)) {
                for (ThreadLocal tl : allThreadLocal) {
                    //备份 业务线程的Tl的value
                    tls.put(tl, tl.get());
                }
            }
        }
    
        /**
         * 恢复备份的业务ThreadLocal到池线程内
         */
        private void recoveryThreadLocal() {
            if (ExecutorThreadUtil.isExecutorThread()) {
                //池线程,恢复业务线程的ThreadLocal
                for (Map.Entry<ThreadLocal, Object> entry : tls.entrySet()) {
                    //此时的Thread,是池线程
                    entry.getKey().set(entry.getValue());
                }
            }
        }
    
        /**
         * 移除池线程中的业务ThreadLocal,防止ThreadLocal数据泄漏
         */
        private void removeThreadLocal() {
            if (ExecutorThreadUtil.isExecutorThread()) {
                //池线程,移除恢复的业务ThreadLocal
                for (Map.Entry<ThreadLocal, Object> entry : tls.entrySet()) {
                    entry.getKey().remove();
                }
            }
        } 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    通过上述优化,解决了多个ThreadLocal传递的问题,同时优化了代码结构,但是最严重的一个问题没有解决,即:run方法执行期间,你不知道需要业务线程的哪些ThreadLocal,尤其是当run方法内部,调用了其他服务,不确定用到哪些,就不确定构造函数的入参,run方法存在获取ThreadLocal对应值为null的风险。

    最好是可以直接把业务线程的所有ThreadLocal全部备份,这样就不需要考虑run方法中需要哪些业务线程的ThreadLocal。
    即修改ThreadLocalRunnable的backupThreadLocal方法。

    private void backupThreadLocal() {
            Map<ThreadLocal, Object> allThreadLocals = Thread.currentThread().threadLocalMap;
            if (!CollectionUtils.isEmpty(allThreadLocals)) {
                for (Map.Entry<ThreadLocal, Object> entry : allThreadLocals.entrySet()) {
                    //备份 业务线程的Tl的value
                    tls.put(entry.getKey(), entry.getValue());
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    即创建任务时,直接获取当前线程的所有ThreadLocal,这样就不需要创建任务时,把业务的ThreadLocal作为入参了。

    上述backupThreadLocal方法是理想中的好方法,但是Thread的ThreadLocalMap不允许跳过ThreadLocal获取值,因此可以把优化点放在:获取当前线程的所有ThreadLocal

    ThreadLocalMap暴露访问

    暴露ThreadLoalMap,

    1. 继承Thread(直接访问)
      通过ThreadLocalMap的源码查看,ThreadLocalMap的访问权限是:default,只允许包内类访问,不允许子类访问,因此通过继承Thread访问ThreadLocalMap行不通。
    2. 继承ThreadLocal(间接访问)
      通过继承ThreadLocal,重写set、get、remove方法,保存ThreadLocal变量,通过变量访问值,间接的获取当前线程的所有ThreadLocal。

    自定义MyTTL

    通过上述分析,只能通过继承ThreadLocal,持有所有的ThreadLocal才能实现间接的访问ThreadLocalMap。

    public class MyTTL<V> extends ThreadLocal<V> {
    
        //1. 继承ThreadLocal,当有set/get/remove操作时,更新MyTTL记录(增删)。
        //2. 为了持有所有MyTTL变量,因此需要是静态变量,保证记录是唯一一份。
        //3. Set集合:保证MyTTL唯一,MyTTL变量可能多次set/get/remove操作
        //4. 通过ThreadLocalsHolder持有线程的所有TTL变量,这样可以通过访问threadLocalsHolder
        // 获取线程所有的MyTTL变量。
        //5. 因为MyTTL继承ThreadLocal支持多线程,因此通过ThreadLocal区分不同线程的所有ThreadLocal
        //6. threadLocalsHolder持有了所有线程的所有MyTTL变量
        private static ThreadLocal<Set<MyTTL>> threadLocalsHolder = new ThreadLocal() {
            /**初始值*/
            protected Set<MyTTL> initialValue() {
                return new HashSet<>();
            }
        };
    
        /**
         * 设置数据,因为是继承ThreadLocal,因此需要先设置父类
         *
         * @param v
         */
        public void set(V v) {
            super.set(v);
            if (v == null) {
                //MyTTL值设置为null,相当于移除MyTTL。
                removeHolder();
            }
            //添加
            add();
        }
    
        /**
         * 记录ThreadLocal
         */
        private void add() {
            threadLocalsHolder.get().add(this);
        }
    
        /**
         * 获取数据
         */
        public V get() {
            V v = super.get();
            if (v == null) {
                removeHolder();
            }
            return v;
        }
    
        private void removeHolder() {
            threadLocalsHolder.get().remove(this);
        }
    
        /**
         * 生成当前Thread的所有ThreadLocal的备份(快照)
         */
        public static Map<MyTTL, Object> backup() {
            Set<MyTTL> currentTls = threadLocalsHolder.get();
            if (currentTls.isEmpty()) {
                return new HashMap<>();
            }
            Map<MyTTL, Object> backup = new HashMap<>();
            for (MyTTL ttl : currentTls) {
                backup.put(ttl, ttl.get());
            }
            return backup;
        }
    
        /**
         * 根据ThreadLocals,恢复当前线程
         */
        public static void resetThreadLocal(Map<MyTTL, Object> allThreadLocals) {
            //重建MyTTL
            for (Map.Entry<MyTTL, Object> entry : allThreadLocals.entrySet()) {
                MyTTL tl = entry.getKey();
                //当前线程添加TTL
                tl.set(entry.getValue());
            }
            //移除不存在的MyTTL
            for (MyTTL myTTL : threadLocalsHolder.get()) {
                if (!allThreadLocals.containsKey(myTTL)) {
                    threadLocalsHolder.get().remove(myTTL);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    上述代码还存在一定的缺陷:threadLocalsHolder强应用MyTTL,在ThreadLocal章节中,就已经提到ThreadLocalMap的key是弱引用,防止强引用导致ThreadLocal对象无法回收,而threadLocalsHolder现在就是强引用MyTTL对象,也存在上述问题,因此threadLocalsHolder的Set中key要改为弱引用,但是没有WeakHashSet,只有WeakHashMap,又因为WeakHashMap也能实现set效果,因此直接使用WeakHashMap代替。

    优化MyTTL的强引用

    public class MyTTL<V> extends ThreadLocal<V> {
    
        //1. 继承ThreadLocal,当有set/get/remove操作时,更新MyTTL记录(增删)。
        //2. 为了持有所有MyTTL变量,因此需要是静态变量,保证记录是唯一一份。
        //3. Set集合:保证MyTTL唯一,MyTTL变量可能多次set/get/remove操作
        //4. 通过ThreadLocalsHolder持有线程的所有TTL变量,这样可以通过访问threadLocalsHolder
        // 获取线程所有的MyTTL变量。
        //5. 因为MyTTL继承ThreadLocal支持多线程,因此通过ThreadLocal区分不同线程的所有ThreadLocal
        //6. threadLocalsHolder持有了所有线程的所有MyTTL变量
        //7. 为了解决threadLocalsHolder强引用MyTTL对象,导致MyTTL对象无法回收的问题,
        //   修改为弱引用,又因为不存在WeakHashSet,只好用WeakHashMap代替,但是相关的value设置为null
        private static ThreadLocal<Map<MyTTL<?>, ?>> threadLocalsHolder = new ThreadLocal() {
            /**初始值*/
            protected WeakHashMap<MyTTL<?>, ?> initialValue() {
                return new WeakHashMap();
            }
        };
    
        /**
         * 设置数据,因为是继承ThreadLocal,因此需要先设置父类
         *
         * @param v
         */
        public void set(V v) {
            super.set(v);
            if (v == null) {
                //MyTTL值设置为null,相当于移除MyTTL。
                removeHolder();
            }
            //添加
            add();
        }
    
        /**
         * 记录ThreadLocal
         */
        private void add() {
            //实现Set效果
            if (!threadLocalsHolder.get().containsKey(this)) {
                threadLocalsHolder.get().put(this, null);
            }
        }
    
        /**
         * 获取数据
         */
        public V get() {
            V v = super.get();
            if (v == null) {
                removeHolder();
            }
            return v;
        }
        
    	 /**
         * 移除
         */
        public void remove() {
            super.remove();
            removeHolder();
        }
    
        private void removeHolder() {
            threadLocalsHolder.get().remove(this);
        }
    
        /**
         * 生成当前Thread的所有ThreadLocal的备份(快照)
         */
        public static Map<MyTTL, Object> backup() {
            Map<MyTTL<?>, ?> currentTls = threadLocalsHolder.get();
            if (currentTls.isEmpty()) {
                return new HashMap<>();
            }
            Map<MyTTL, Object> backup = new HashMap<>();
            for (Map.Entry<MyTTL<?>, ?> entry : currentTls.entrySet()) {
                backup.put(entry.getKey(), entry.getKey().get());
            }
            return backup;
        }
    
        /**
         * 根据ThreadLocals,恢复当前线程
         */
        public static void resetThreadLocal(Map<MyTTL, Object> allThreadLocals) {
            //重建MyTTL
            for (Map.Entry<MyTTL, Object> entry : allThreadLocals.entrySet()) {
                MyTTL tl = entry.getKey();
                //当前线程添加TTL
                tl.set(entry.getValue());
            }
            //移除不存在的MyTTL
            for (MyTTL myTTL : threadLocalsHolder.get().keySet()) {
                if (!allThreadLocals.containsKey(myTTL)) {
                    threadLocalsHolder.get().remove(myTTL);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    优化MyTTL的继承

    MyTTL继承自ThreadLocal,ThreadLocal本身是不能处理父子线程的传递问题,而InheritableThreadLocal可以实现父子线程间的传递,因为MyTTL继承修改为InheritableThreadLocal。

    public class MyTTL<V> extends InheritableThreadLocal<V> {}
    
    • 1

    这样TTL既支持父子线程的传递,也支持线程池的线程复用。

    MyTTLRunnbale

    在ThreadLocal的传递时机中,描述了如何传递的问题,因此还需要特殊的Runnable接口配合MyTTL才能实现传递功能。

    public class MyTTLRunnable implements Runnable {
        //实际的run方法
        private Runnable runnable;
        //存储业务线程的所有MyTTL,此时可以看做是快照
        //业务线程后续再添加的MyTTL,run方法方位时,仍然为null
        private Map<MyTTL, Object> bizThreadLocals= new HashMap<>();
    
        public MyTTLRunnable(Runnable runnable) {
            this.runnable = runnable;
            //备份业务线程的MyTTL
            bizThreadLocals= MyTTL.backup();
        }
    
        @Override
        public void run() {
            //备份池线程的MyTTL
            Map<MyTTL, Object> currentThreadBackup = MyTTL.backup();
            try {
                //恢复业务MyTTL到池线程中
                MyTTL.resetThreadLocal(bizThreadLocals);
                runnable.run();
            } finally {
                //恢复池线程的MyTTL
                MyTTL.resetThreadLocal(currentThreadBackup);
            }
        }
    
        public static MyTTLRunnable of(Runnable runnable) {
            return new MyTTLRunnable(runnable);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    测试

    public class MyTTLDemo {
        private static MyTTL<String> currentUser = new MyTTL<>();
        private static ExecutorService executorService = Executors.newFixedThreadPool(1);
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                //模拟10个请求
                String user = "user_" + i;
                currentUser.set(user);
                executorService.execute(MyTTLRunnable.of(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName() + ":" + currentUser.get());
                    }
                }));
            }
            executorService.shutdown();
        }
    }
    
    输出:
    pool-1-thread-1:user_0
    pool-1-thread-1:user_1
    pool-1-thread-1:user_2
    pool-1-thread-1:user_3
    pool-1-thread-1:user_4
    pool-1-thread-1:user_5
    pool-1-thread-1:user_6
    pool-1-thread-1:user_7
    pool-1-thread-1:user_8
    pool-1-thread-1:user_9
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    启动只有一个线程的线程池,输出了不同的请求的用户信息,这就表明MyTTL支持线程池的复用。

    优化

    上述测试是普通线程池运行MyTTLRunnable,如果需要使用MyTTL,就需要变动普通的Runnable为MyTTLRunnable,改动可能很大,因此可以换个角度:MyTTL线程池,运行普通Runnable。

    public class TTLExecutorService extends ThreadPoolExecutor {
    
        public TTLExecutorService(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        }
    
        /**
         * 重写execute,内部把Runnable替换为MyTTLRunnable
         */
        public void execute(Runnable command) {
            super.execute(MyTTLRunnable.of(command));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    继承ThreadPoolExecutor,重新execute方法,这样使用就比较方便。

    个人思考

    池线程的MyTTL恢复,有两种方案:1 备份恢复。2 用完删除。

    1. 备份恢复
      先备份池线程的MyTTL,恢复业务MyTTL到池线程,然后再根据备份的MyTTL,恢复池线程的TTL。这样可以完整的保证池线程的MyTTL正确性。
    2. 用完删除:恢复业务MyTTL到池线程,完成后,移除池线程的业务MyTTL。
      方案一:存在数据copy和替换的问题,不过都是浅拷贝,速度还可控。
      方案二存在严重的后果:线程池存在callerRun拒绝策略,如果触发,此时的池线程却是业务线程本身,如果移除了业务MyTTL,就导致业务线程后续无法继续使用。

    但是如果能区分出池线程和业务线程,那么方案二是否可行?

    业务TTL用完删除

    其他与MyTTLRunnable一致
    public class MyTTLRunnableTwo implements Runnable {
        @Override
        public void run() {
            try {
                //恢复业务MyTTL到池线程中
                MyTTL.resetThreadLocal(bizThreadLocals);
                runnable.run();
            } finally {
                //删除业务TTL
                removeBizMyTTL(bizThreadLocals);
            }
        }
    
        private void removeBizMyTTL(Map<MyTTL, Object> bizThreadLocals) {
            if (ExecutorThreadUtil.isExecutorThread()) {
                //只有池线程才会移除
                for (MyTTL myTTL : bizThreadLocals.keySet()) {
                    myTTL.remove();
                }
            }
        }   
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    核心是如何ExecutorThreadUtil.isExecutorThread()。线程池是支持ThreadFactory,那么是否可以通过ThreadFactory创建线程时标明。

    public class MyTTLRunnableTwoDemo {
        private static MyTTL<String> currentUser = new MyTTL<>();
        private static ExecutorService executorService;
    
        static {
            executorService = Executors.newFixedThreadPool(1);
            //线程池指定ThreadFactory
            ((ThreadPoolExecutor) executorService).setThreadFactory(new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    //当前线程设置为池线程
                    ExecutorThreadUtil.set();
                    t.setName("pool_" + t.getName());
                    return t;
                }
            });
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                //模拟10个请求
                String user = "user_" + i;
                currentUser.set(user);
                executorService.execute(MyTTLRunnableTwo.of(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName() + ":" + currentUser.get());
                    }
                }));
            }
            executorService.shutdown();
        }
    }
    
    输出:
    pool_Thread-0:user_0
    pool_Thread-0:user_1
    pool_Thread-0:user_2
    pool_Thread-0:user_3
    pool_Thread-0:user_4
    pool_Thread-0:user_5
    pool_Thread-0:user_6
    pool_Thread-0:user_7
    pool_Thread-0:user_8
    pool_Thread-0:user_9
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
  • 相关阅读:
    TCP/IP网络参考模型
    华为ModelArts分布式训练教程
    从真实案例出发,全方位解读 NebulaGraph 中的执行计划
    【docker命令】
    Sed流编辑器总结
    Appium混合页面点击方法tap的使用
    matlab simulink 电网扫频仿真和分析
    12月2日第壹简报,星期五,农历十一月初九
    C++布隆过滤器和哈西切分
    GODIVA论文阅读
  • 原文地址:https://blog.csdn.net/u010652576/article/details/126925292