溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java高并發(fā)中如何進行線程封閉

發(fā)布時間:2021-10-19 16:17:48 來源:億速云 閱讀:117 作者:柒染 欄目:大數(shù)據(jù)

這篇文章給大家介紹java高并發(fā)中如何進行線程封閉,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

很多時候都想躲避并發(fā),避免并發(fā)除了設(shè)計成不可變對象其實還有一個簡單的方法就是線程封閉。

什么是線程封閉?

其實就是把對象封裝到一個線程里,只有這一個線程能看到這個對象。那么這個對象就算不是線程安全的,也不會出現(xiàn)線程安全方面的問題了,因為他只能在一個線程里面進行訪問。那么如何實現(xiàn)線程封閉呢?

如何實現(xiàn)線程封閉?

  • Ad-hoc 線程封閉:程序控制實現(xiàn),最糟糕,忽略

  • 堆棧封閉:局部變量,無并發(fā)問題。是我們現(xiàn)實中使用最多的封閉了,簡單說就是局部變量。多個線程訪問一個方法的時候,方法中的局部變量都會被拷貝一份到線程棧中,所以局部變量是不會被線程共享的,因此不會出現(xiàn)并發(fā)問題。所以全局變量容易引起并發(fā)問題。

  • ThreadLocal線程封閉:特別好的封閉方法。ThreadLocal內(nèi)部維護了一個map,key是線程的名稱,value就是封閉的對象。

 ThreadLocal

正常來講我們每一個請求對服務(wù)器來講都是一個線程在運行,我們希望線程間隔離,一個線程在被后端服務(wù)器實際處理的時候,可以通過Filter過濾器取出當前的用戶,然后將數(shù)據(jù)存放在ThreadLocal中,當線程被接口的service以及其他相關(guān)類進行處理的時候很可能需要在取出當前用戶,這時就可以隨時隨地從ThreadLocal中直接拿到之前存儲的值這樣用起來就很方便了。如果我們不這樣做,會有什么麻煩呢?因為我們的登錄用戶通常是從request中取出來的,因此需要帶上request或者從request中取出來的用戶信息,從controller層開始不停的往下傳,甚至會傳到一些util類中,這樣會使得代碼看起來很臃腫。當使用ThreadLocal和Filter,就可以很方便的在接口處理之前,前取出相關(guān)的信息,在接口實際處理的時候,什么時候需要什么時候再把信息取出來,這樣代碼在設(shè)計的時候就容易多了,不至于把request從controller一直傳遞下去。

具體使用實例如下:

新建一個類:

public class RequestHolder {
    //因為當前沒有登錄用戶,我們用線程id來充當
    private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>();

    /**
     * 請求進入到后端服務(wù)器,但是還沒有實際處理的時候調(diào)用add,可以使用Filter
     * @param id
     */
    public static void add(Long id) {
        //雖然只傳入id,但是threadLocal會取出當前線程id放到map中的key,value是傳入的值
        requestHolder.set(id);
    }

    public static Long getId() {
        return requestHolder.get();
    }

    /**
     * 如果不做remove的話,會造成內(nèi)存泄漏,數(shù)據(jù)永遠不會釋放掉
     * 需要在接口真正處理完成之后進行調(diào)用,可以使用interceptor
     */
    public static void remove() {
        requestHolder.remove();
    }
}

這個類就用來存放ThreadLocal。

新建一個Filter:

@Slf4j
public class HttpFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//        httpServletRequest.getSession().getAttribute("user");
        log.info("do filter , {}, {}", Thread.currentThread().getId(), ((HttpServletRequest) request).getServletPath());
        RequestHolder.add(Thread.currentThread().getId());
        // 如果這個Filter不想攔截住這個請求,只想做單獨的數(shù)據(jù)處理時,要調(diào)用chain.doFilter,使得攔截器處理完
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

新建一個Interceptor:

@Slf4j
public class HttpInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle");
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        RequestHolder.remove();
        log.info("afterCompletion");
        return ;
    }
}

配置filter和interceptor:

@Configuration
public class Config implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean<HttpFilter> httpFilter(){
        FilterRegistrationBean<HttpFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        // 設(shè)置filter
        filterRegistrationBean.setFilter(new HttpFilter());
        // 攔截規(guī)則
        filterRegistrationBean.addUrlPatterns("/threadLocal/*");
        return filterRegistrationBean;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**");
    }
}

新建一個controller進行測試:

@RestController
@RequestMapping("threadLocal")
public class ThreadLocalController {

    @GetMapping("/test")
    public Long test() {
        return RequestHolder.getId();
    }

}

在瀏覽器中輸入localhost:8080/threadLocal/test可以輸出線程的id,與后臺的輸出id一致。

關(guān)于java高并發(fā)中如何進行線程封閉就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI