美文网首页
使用ThreadLocal设计一个上下文设计模式

使用ThreadLocal设计一个上下文设计模式

作者: herohua | 来源:发表于2020-01-17 13:37 被阅读0次

    ThreadLocal:线程保险箱,jdk官方定义

    ThreadLocal说明.png

    Thread是线程私有的,每个线程都有其独立的副本变量,通过get/set方法设置/获取变量。
    只要线程是活动的并且ThreadLocal实例是可访问的,则每个线程都对其线程局部变量的副本持有隐式引用。 线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)。
    Thread是线程私有的,每个线程都有其独立的副本变量,通过get/set方法设置/获取变量。

    简单使用:

    public class ThreadLocalTest {
    
        private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() -> {
                threadLocal.set("thread-0");
                System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
            });
    
    
            Thread t2 = new Thread(() -> {
                threadLocal.set("thread-1");
                System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
            });
    
            t1.start();
            t2.start();
        }
    }
    

    运行结果:


    运行结果.png

    可以看出每个线程的变量对其它线程都是不可见的,通过get方法只能获取自己线程通过set方法放入到ThreadLocal中的变量,如果没有调用过set方法,则获取的结果是null。也可以给ThreadLcoal设置一个初始值,并且ThreadLocal是一个泛型类。

    public class ThreadLocalSimpleTest {
    
        private static ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
            // 设置初始值
            @Override
            protected String initialValue() {
                return "Alex";
            }
        };
    
        public static void main(String[] args) {
            System.out.println(threadLocal.get());
    
            threadLocal.set("Blex");
    
            System.out.println(threadLocal.get());
        }
    }
    

    运行结果:


    ThreadLocal设置初始值.png

    第一次调用get方法得到的是初始值,第二次调用get方法获取的是set方法设置的值。

    上下文设计模式

    对于一个线程,可能有多个任务,分为多个执行步骤,如果第N个执行步骤需要用到第一个执行步骤中的一个变量,常见的方法是设置一个上下文Context,将这个变量放到上下文Context中,通过方法入参将Context传递下去,从而第N个步骤可以用到第一个步骤传过来的变量。但是如果方法过多,每个方法都需要加这样的以一个参数,是不合理的。此时就可以运用ThreadLocal来改善上下文设计模式。
    具体的方案就是将上下文Context放到ThreadLocal中,从而在不用传递Context的前提下,也能访问到上下文Context,从而可以轻松对Context设置/获取里面的变量。

    上下文Context定义:

    public class Context {
    
        private String name;
    
        private String cardId;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getCardId() {
            return cardId;
        }
    
        public void setCardId(String cardId) {
            this.cardId = cardId;
        }
    }
    

    通过ThreadLocal包装Context:

    public final class ActionContext {
    
        private final static ThreadLocal<Context> threadLocal = new ThreadLocal<Context>() {
            @Override
            protected Context initialValue() {
                // 在ThreadLocal中放入一个初始值
                return new Context();
            }
        };
    
        private ActionContext() {}
    
        private static class ActionContextHolder {
            final static ActionContext actionContext = new ActionContext();
        }
    
        public static ActionContext getActionContext() {
            return ActionContextHolder.actionContext;
        }
    
        public static Context getContext() {
            return threadLocal.get();
        }
    }
    

    定义线程执行的单元:

    public class ExecutionTask implements Runnable {
    
        private QueryFromDBAction queryFromDBAction = new QueryFromDBAction();
    
        private QueryFromHttpAction queryFromHttpAction = new QueryFromHttpAction();
    
        @Override
        public void run() {
            // 步骤一:从DB中查询name
            queryFromDBAction.execute();
            System.out.println("The name query successful.");
            // 步骤二:http方式查询cardId
            queryFromHttpAction.execute();
            System.out.println("The cardId query successful.");
    
            // 省略其他步骤
    
            // 获取上下文
            Context context = ActionContext.getActionContext().getContext();
            System.out.println("name:" + context.getName());
            System.out.println("cardId:" + context.getCardId());
        }
    }
    

    QueryFromDBAction:

    public class QueryFromDBAction {
    
        public void execute() {
            try {
                Thread.sleep(1000L);
                String name = getName();
                // 放入到线程上下文
                ActionContext.getActionContext().getContext().setName(Thread.currentThread().getName() + "->" + name);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    
        private String getName() {
            return "HuaJian";
        }
    }
    

    QueryFromHttpAction:

    public class QueryFromHttpAction {
    
        public void execute() {
            try {
                Thread.sleep(1000L);
                String cardId = getCardId();
                // // 放入到线程上下文
                ActionContext.getActionContext().getContext().setCardId(Thread.currentThread().getName() + "->" + cardId);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        private String getCardId() {
            return "1223456789";
        }
    }
    

    客户端创建5个线程运行:

    public class ContextTest {
    
        public static void main(String[] args) {
            IntStream.range(1, 5)
                    .forEach(
                            i -> new Thread(new ExecutionTask()).start()
                    );
        }
    }
    

    测试结果:


    测试结果.png

    可以看出,5个线程通过get方法取到的上下文都是不一样的,说明ThreadLocal是线程私有的。

    相关文章

      网友评论

          本文标题:使用ThreadLocal设计一个上下文设计模式

          本文链接:https://www.haomeiwen.com/subject/mmyczctx.html