หน้าเว็บ

วันพุธที่ 5 กุมภาพันธ์ พ.ศ. 2557

ThreadLocal คืออะไร? : java


ThreadLocal
  • เป็น java class ที่ใช้สำหรับจัดเก็บข้อมูลใดๆ ภายใต้ scope ของ Thread ปัจจุบันที่กำลังทำงานอยู่ (global scope of current thread) 
  • สามารถ access ข้อมูลที่จัดเก็บไว้ได้โดยตรง  ซึ่ง access จากที่ใดก็ได้ (any where) ผ่านทาง ThreadLocal โดยที่ไม่ต้องส่งต่อข้อมูลผ่านทาง method ไปเรื่อยๆ ตามลำดับ (ที่ใดก็ได้ ในที่นี้หมายถึงส่วนนั้น ต้องทำงานอยู่บน Thread เดียวกันด้วย)

ตัวอย่างเช่น การทำ ClientNotification ซึ่ง มีการเรียกใช้ HttpServletResponse
แน่นอนว่า ถ้าเราต้องการ notify ไปยัง client ตัวใด  เราก็ต้องรู้ response ของ client ตัวนั้นเสมอ

(ปัญหา)
การเขียนแบบเดิม คือส่งต่อข้อมูลผ่านทาง method ไปเรื่อยๆ
CommandManager.java
package com.pamarin.javascript.uicommand;

import java.io.IOException;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author recrow
 */
public class CommandManager {

    private static String managerName = "com.pamarin.core.behavioral.CommandManager";

    public static void setName(String name) {
        managerName = name;
    }

    public static String getName() {
        return managerName;
    }

    public static void executeCommand(Command command, HttpServletResponse response) {
        try {
            response.setContentType("text/javascript");
            response.getWriter().print(new StringBuilder()
                    .append("(function(){ ")
                    .append(managerName)
                    .append(".executeCommand('")
                    .append(command.getCommandName())
                    .append("', ")
                    .append(JSONUtils.toJSON(command))
                    .append("); }).call(this);")
                    .toString());
        } catch (IOException ex) {
            //
        }
    }
}
ClientNotification.java
package com.pamarin.javascript.uicommand;

import com.pamarin.javascript.context.Clients;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author redcrow
 */
public class ClientNotification {

    public static void notify(String title, String content, HttpServletResponse response) {
        GrowlCommand command = new GrowlCommand();
        command.setContent(content);
        command.setTitle(title);
        command.setSticky(Boolean.FALSE);
        command.setTime(5000L);
        command.setImage("");
        command.setStyleClass("");
        //
        notify(command, response);
    }
    
    public static void notify(GrowlCommand command, HttpServletResponse response){
        CommandManager.executeCommand(command, response);
    }
}
การใช้งาน
HttpServletResponse response = ...

ClientNotification.notify("delete post", "delete success.", response);
จาก code ข้างบน จะเห็นว่าเราต้องส่งต่อ HttpServletResponse ผ่านทาง method ไปเรื่อยๆ เพียงเพื่อ getWriter().print() ซึ่งเป็นวิธีที่ผู้เขียนคิดว่ามันไม่ work ครับ

แล้วถ้าเราไม่ส่งต่อล่ะ  เราจะทำยังไง?
คำตอบคือใช้ ThreadLocal 


(วิธีแก้)
แบบใหม่ ใช้ ThreadLocal
ปรับปรุง code ใหม่
RequestContext.java
package com.pamarin.javascript.context;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author redcrow
 */
public class RequestContext {

    private final HttpServletRequest request;
    private final HttpServletResponse response;

    public RequestContext(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;
    }

    public HttpServletRequest getRequest() {
        return request;
    }

    public HttpServletResponse getResponse() {
        return response;
    }

}
ClientContext.java
package com.pamarin.javascript.context;

/**
 *
 * @author redcrow
 */
public class ClientContext {

    private RequestContext requestContext;

    public RequestContext getRequestContext() {
        return requestContext;
    }

    public void setRequestContext(RequestContext requestContext) {
        this.requestContext = requestContext;
    }

}
Clients.java  (สำหรับใช้ get / set ค่าของThread ปัจจุบัน)
package com.pamarin.javascript.context;

/**
 *
 * @author jittagornp
 */
public class Clients {

    private static final ThreadLocal<ClientContext> CONTEXT = new ThreadLocal<ClientContext>();

    public static void setContext(ClientContext context) {
        CONTEXT.set(context);
    }

    public static ClientContext getContext() {
        return CONTEXT.get();
    }

    public static void removeContext() {
        CONTEXT.remove();
    }
}
CommandManager.java
package com.pamarin.javascript.uicommand;

import com.pamarin.javascript.context.Clients;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author redcrow
 */
public class CommandManager {

    private static String managerName = "com.pamarin.core.behavioral.CommandManager";

    public static void setName(String name) {
        managerName = name;
    }

    public static String getName() {
        return managerName;
    }

    public static void executeCommand(Command command) {
        try {
            HttpServletResponse response = Clients.getContext().getRequestContext().getResponse();
            response.setContentType("text/javascript");
            response.getWriter().print(new StringBuilder()
                    .append("(function(){ ")
                    .append(managerName)
                    .append(".executeCommand('")
                    .append(command.getCommandName())
                    .append("', ")
                    .append(JSONUtils.toJSON(command))
                    .append("); }).call(this);")
                    .toString());
        } catch (IOException ex) {
            //
        }
    }
}
ClientNotification.java
package com.pamarin.javascript.uicommand;

/**
 *
 * @author recrow
 */
public class ClientNotification {

    public static void notify(String title, String content) {
        GrowlCommand command = new GrowlCommand();
        command.setContent(content);
        command.setTitle(title);
        command.setSticky(Boolean.FALSE);
        command.setTime(5000L);
        command.setImage("");
        command.setStyleClass("");
        //
        notify(command);
    }
    
    public static void notify(GrowlCommand command){
        CommandManager.executeCommand(command);
    }
}
code ส่วนนี้อาจจะ set ไว้ที่ doFilter ของ servlet filter หรือ preHandle ของ interceptor (Spring MVC) ก็ได้น่ะครับ เพื่อ set current request / response ลงใน ThreadLocal ครับ
        ClientContext clientContext = new ClientContext();
        clientContext.setRequestContext(new RequestContext(request, response));
        Clients.setContext(clientContext);
การใช้งาน
ClientNotification.notify("delete post", "delete success.");
จะเห็นว่าเราไม่ต้องส่งต่อ HttpServletResponse ไปเรื่อยๆ เหมือนอย่างที่เคยทำแล้วน่ะครับ นั่นเป็นเพราะเราใช้ ThreadLocal เข้ามาช่วยแก้ปัญหานี้นั่นเอง

 ตัวอย่างที่เห็นได้ชัดเจนของการใช้ ThreadLocal คือ transaction ของ spring, security context ของ spring, jsf primefaces notify (add message) และอื่นๆ  อีกมากมายครับ

หวังว่าบทความนี้คงช่วยทำให้เข้าใจ concept การใช้งาน ThreadLocal น่ะครับ

ไม่มีความคิดเห็น:

แสดงความคิดเห็น