Java多线程总结(4)— 线程范围内数据操作的隔离及ThreadLocal类

来源:互联网 发布:seo研究中心seo8 编辑:程序博客网 时间:2024/05/21 07:56

1. Java内存模型

  在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本文使用“共享变量”这个术语代指实例域,静态域和数组元素)。局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:
      这里写图片描述
  下面通过简单的案例分析。需求:多个线程独立的操作变量sharedData,每个线程都可修改、获得该变量。现实现每个线程单独修改该变量,且更改的值只能在本线程内可见。

2. static方式实现

  我们知道static变量保存在堆内存中,可以被多个线程所共享。将所要共享的变量用static修饰:

// 利用static实现线程间数据的共享private static int sharedData;public void staticShareValuable() {    for (int i = 0; i < 2; i++) {        new Thread(new Runnable() {            @Override            public void run() {                // 线程范围内修改共享变量                sharedData = new Random().nextInt(100);                new ModelA().get();                new ModelB().get();            }        }).start();    }}// 线程的A模块class ModelA {    public void get(){        System.out.println("ModelA from " + Thread.currentThread().getName() + " 线程 " + " get data :" + sharedData);    }}// 线程的B模块class ModelB {    public void get(){        System.out.println("ModelB from " + Thread.currentThread().getName() + " 线程 " + " get data :" + sharedData);    }}

  运行输出:

ModelA from Thread-1 线程  get data :37ModelA from Thread-0 线程  get data :37ModelB from Thread-1 线程  get data :37ModelB from Thread-0 线程  get data :37

  由结果可知,static这种方式并不能实现线程单独修改该变量,且更改的值只能在本线程内可见。其实这是显然的,只是为了说明:我们在多线程中使用static共享数据的时候一定要考虑这个问题!

3. Map方式模拟ThreadLocal基本原理

  将数据保存在Map集合当中,将当前的Thread实例作为key,当前线程操作的变量作value,这样就实现了每个线程独立的操作变量。即线程操作数据的隔离。

// 模拟ThreadLocal的实现private static Map<Thread, Integer> context = new HashMap<Thread, Integer>();/** *  每个线程独立的实现更改sharedData,更改的值只能在本线程内可见 *  利用传统的static不能实现! */public void mapShareValuable() {    for (int i = 0; i < 2; i++) {        new Thread(new Runnable() {            @Override            public void run() {                // 线程范围内修改共享变量                int copySharedData = new Random().nextInt(100);                // map集合中放入key-value                context.put(Thread.currentThread(), copySharedData);                new ModelA().get();                new ModelB().get();            }        }).start();    }}// 线程的A模块class ModelA {    public void get(){        // map集合中根据key(当前线程实例),取出当前线程所要操作的值        int sharedData = context.get(Thread.currentThread());        System.out.println("ModelA from " + Thread.currentThread().getName() + " 线程 " + " get data :" + sharedData);    }}// 线程的B模块class ModelB {    public void get(){        int sharedData = context.get(Thread.currentThread());        System.out.println("ModelB from " + Thread.currentThread().getName() + " 线程 " + " get data :" + sharedData);    }}

  运行输出:

ModelA from Thread-0 线程  get data :61ModelA from Thread-1 线程  get data :33ModelB from Thread-0 线程  get data :61ModelB from Thread-1 线程  get data :33

  由运行结果可以看出:Thread-0线程的模块A和B获得的数据为Thread-0线程所修改的数据,两个线程单独修改变量,且更改的值只能在本线程内可见。其实这就是ThreadLocal简单原理(只是key值不同,ThreadLocal内部的ThreadLocalMap(a customized hash map)对象,key为ThreadLocal对象)。

4. ThreadLocal

  ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。
  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。如果共享的变量有多个,可以将其封装成对象,放入ThreadLocal中。例如Struts2的ActionContext需要保存当前请求线程的request、response,param,application以及action等等。下面的案例简单模拟了Struts2的ActionContext功能。作为上下文封装当前线程的请求Request操作、响应Response,在Action中可通过ActionContext获取这些对象。

/** * 模拟Struts2的ActionContext * Struts2的ActionContext需要保存当前请求线程的request、response,param,application以及action等等。 * @author markliu * */public class ThreadLocalTest {    public static void main(String[] args) {        // 模拟两次请求        for (int i = 0; i < 10; i++) {            new Thread(new Runnable() {                @Override                public void run() {                    // 以下代码模拟Tomcat封装请求信息等并绑定到对应线程                    Request request = new Request();                    request.setRequestMsg(Thread.currentThread().getName() + "线程请求HelloWorld.action资源");                    Response response = new Response();                    response.setResponseMsg("向" + Thread.currentThread().getName()+"线程" + "返回资源");                    ActionContext context = ActionContext.getContext();                    context.setRequest(request);                    context.setResponse(response);                    // 调用Action的方法                    new HelloWorldAction().execute();                }            }).start();        }    }}class HelloWorldAction {    public String execute() {        ActionContext context = ActionContext.getContext();        System.out.println(Thread.currentThread().getName()+"线程在" +                 "Action中获取的请求信息:" + context.getRequest().getRequestMsg() + "-->" +                "Action中获取的响应信息信息:" + context.getResponse().getResponseMsg());        return "success";    }}/** * 由于保存的数据有多个,需要多个ThreadLocal,可以 * 将需要保存的request、response等信息封装成一个上下文对象 * @author markliu * */class ActionContext {    private static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>();     // 封装实际的request,response等信息    private Map<String, Object> context = new HashMap<String, Object>();    public static String REQUEST = "com.xxx.request";    public static String RESPONSE = "com.xxx.response";    /**     * 获取当前线程所对应的ActionContext     * @return     */    public static ActionContext getContext() {        // 从ThreadLocal中获取当前线程对应的上下文对象        ActionContext instance = actionContext.get();        if(instance == null){ // 如果首次请求,为null            instance = new ActionContext();            actionContext.set(instance);        }        return instance;    }    // 从ThreadLocal移除当前线程的相关联的上下文对象    public void remove() {        actionContext.remove();    }    public void setRequest(Request request) {        context.put(REQUEST, request);    }    public Request getRequest() {        return (Request) context.get(REQUEST);    }    public void setResponse(Response response) {        context.put(RESPONSE, response);    }    public Response getResponse() {        return (Response) context.get(RESPONSE);    }}class Request {    private String requestMsg;    public String getRequestMsg() {        return requestMsg;    }    public void setRequestMsg(String requestMsg) {        this.requestMsg = requestMsg;    }}class Response {    private String responseMsg;    public String getResponseMsg() {        return responseMsg;    }    public void setResponseMsg(String responseMsg) {        this.responseMsg = responseMsg;    }}

  运行结果如下:
    这里写图片描述
  由此可见,实现了不同的线程分别处理各自线程范围内的Request和Response。
注意:
1. 上面案例的ThreadLocal实例只有一个。
2. 当线程结束时,要调用ThreadLocal的remove()方法,从thread-local variable中移除。

  转载请注明出处:线程范围内的数据共享及ThreadLocal

1 0
原创粉丝点击