美文网首页
使用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