localBlock在java_Java_threadLocal.html

var NexT = window.NexT || {};

var CONFIG = {

root: '/blog/',

scheme: 'Mist',

version: '5.1.4',

sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},

fancybox: true,

tabs: true,

motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},

duoshuo: {

userId: '0',

author: '博主'

},

algolia: {

applicationID: '',

apiKey: '',

indexName: '',

hits: {"per_page":10},

labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}

}

};

一探究竟源计划--ThreadLocal | Lenoy聆羽

Lenoy聆羽

刘超的个人博客,java开发工程师

一探究竟源计划--ThreadLocal

在我们日常 Java Web 开发中难免遇到需要把一个参数层层的传递到最内层,然后中间层根本不需要使用这个参数,或者是仅仅在特定的工具类中使用,这样我们完全没有必要在每一个方法里面都传递这样一个通用的参数。
而在Java中每一个线程都独享一个ThreadLocal,在接收请求的时候set特定内容,在需要的时候get这个值,恰好可以实现这样的需求。

ThreadLocal主要用来提供线程局部变量,就是为每一个使用该变量的线程都提供一个变量值的副本,这个变量只对当前线程可见,只在线程的生命周期内起作用,每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

维持线程封闭性的一种更规范的方法就是使用ThreadLocal,这个类能使线程中的某个值与保存的值的对象关联起来。ThreadLocal提供get和set等接口或方法,这些方法为每一个使用这个变量的线程都存有一份独立的副本,因此get总是返回由当前线程在调用set时设置的最新值。

首先我们来看个例子:

123456789101112131415161718192021222324252627
public final class ConnectionUtil {    private ConnectionUtil() {}    private static final ThreadLocal<Connection> conn = new ThreadLocal<>();    public static Connection getConn() {        Connection con = conn.get();        if (con == null) {            try {                Class.forName("com.mysql.jdbc.Driver");                con = DriverManager.getConnection("url", "userName", "password");                conn.set(con);            } catch (ClassNotFoundException | SQLException e) {                // ...            }        }        return con;    }    public static void closeConnection() {        Connection con = conn.get();        if(con!=null){            con.close();        }    }}

每个线程调用getConn()都使用的是同一个Connection,但是每个线程获取到的都是同一个连接的副本,这样就能很好的解决线程安全问题。

ThreadLocal原理

在Java中,每个线程内部都会维护一个自有的ThreadLocalMap ,用来存储本线程私有的数据。

123
class Thread implements Runnable {    ThreadLocal.ThreadLocalMap threadLocals = null;}

ThreadLocalMap类的定义是在ThreadLocal类中,真正的引用却是在Thread类中,同时ThreadLocalMap中定义了用于存储数据的Entry结构:

123456789
static class ThreadLocalMap {    static class Entry extends WeakReference<ThreadLocal<?>> {        Object value;        Entry(ThreadLocal<?> k, Object v) {            super(k);            value = v;        }    }}

可以看到这个Map的key是ThreadLocal类的实例对象,value为用户的值。每次创建一个ThreadLocalMap对象并且设置值的时候,就会往线程内部私有的ThreadLocalMap中保存这个值,key是创建的ThreadLocal对象,value为用户的值。

一个线程可以有多个ThreadLocal对象,每一个ThreadLocal对象是如何区分的呢?

123456
private final int threadLocalHashCode = nextHashCode();private static AtomicInteger nextHashCode = new AtomicInteger();private static final int HASH_INCREMENT = 0x61c88647;private static int nextHashCode() {    return nextHashCode.getAndAdd(HASH_INCREMENT);}

对于每一个ThreadLocal对象,都有一个final修饰的int型的threadLocalHashCode属性,对于基本数据类型,可以认为它在初始化后就不可以进行修改,所以可以唯一确定一个ThreadLocal对象。

在ThreadLocal类中,还包含了一个static修饰的AtomicInteger成员变量(即类变量)和一个static final修饰的常量(作为两个相邻nextHashCode的差值)。由于nextHashCode是类变量,所以每一次创建ThreadLocal对象都可以保证nextHashCode被更新到新的值,并且下一次创建ThreadLocal对象时这个被更新的值仍然可用,同时AtomicInteger保证了nextHashCode自增的原子性。

set()方法
1234567891011121314151617181920212223242526272829303132333435363738394041424344
ThreadLocal:public void set(T value) {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);}void createMap(Thread t, T firstValue) {    t.threadLocals = new ThreadLocalMap(this, firstValue);}ThreadLocalMap:private void set(ThreadLocal<?> key, Object value) {    Entry[] tab = table;    int len = tab.length;    /*根据 ThreadLocal 的散列值,查找对应元素在数组中的位置,这里对 len-1 进行了取余操作。    * 之所以能这样取余是因为 len 的值比较特殊,是 2 的 n 次方    */    int i = key.threadLocalHashCode & (len-1);    // 使用线性探测法查找元素    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {        ThreadLocal<?> k = e.get();        // ThreadLocal 对应的 key 存在,直接覆盖之前的值        if (k == key) {            e.value = value;            return;        }        // key为 null,但是值不为 null,说明之前的 ThreadLocal 对象已经被回收了,        // 当前数组中的 Entry 是一个陈旧(stale)的元素        if (k == null) {            // 用新元素替换陈旧的元素,这个方法进行了不少的垃圾清理动作,防止内存泄漏            replaceStaleEntry(key, value, i);            return;        }    }    // ThreadLocal 对应的 key 不存在并且没有找到陈旧的元素,则在空元素的位置创建一个新的 Entry。    tab[i] = new Entry(key, value);    int sz = ++size;    // cleanSomeSlot 清理陈旧的 Entry(key == null),如果没有清理陈旧的Entry 并且数组中的元素大于了阈值,则进行 rehash。    if (!cleanSomeSlots(i, sz) && sz >= threshold){        rehash();    }}
get()方法
1234567891011121314151617181920212223242526272829303132333435363738394041
ThreadLocal:public T get() {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null) {        ThreadLocalMap.Entry e = map.getEntry(this);        if (e != null) {            @SuppressWarnings("unchecked")            T result = (T)e.value;            return result;        }    }    return setInitialValue();}ThreadLocalMap getMap(Thread t) {    return t.threadLocals;}ThreadLocalMap:private Entry getEntry(ThreadLocal <?> key) {    int i = key.threadLocalHashCode & (table.length - 1);    Entry e = table[i];    if (e != null && e.get() == key)        return e;    else        return getEntryAfterMiss(key, i, e);}private Entry getEntryAfterMiss(ThreadLocal <?> key, int i, Entry e) {    Entry[] tab = table;    int len = tab.length;    while (e != null) {        ThreadLocal < ? > k = e.get();        if (k == key)            return e;        if (k == null)            expungeStaleEntry(i);        else            i = nextIndex(i, len);        e = tab[i];    }    return null;}

因为 ThreadLocalMap 中采用开放定址法,所以当前 key 的散列值和元素在数组中的索引并不一定完全对应。所以在 get 的时候,首先会看 key 的散列值对应的数组元素是否为要查找的元素,如果不是,再调用 getEntryAfterMiss 方法查找后面的元素。

ThreadLocal的内存泄露问题

根据Entry的源码,我们知道ThreadLocalMap是使用ThreadLocal的弱引用作为Key的(关于对象引用方式可以查看我的另一篇文章Java的四种引用方式)。如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,如果当前线程再迟迟不结束的话,这些key为null的Entry将无法回收,造成内存泄露。
所以JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。

应用场景

ThreadLocal 最常见的使用场景有解决数据库连接、Session管理等。

数据库连接

JDBC连接保存在ThreadLocal对象中,每个对象都有属于自己的连接,代码如下:

12345678
private static ThreadLocal < Connection > connectionHolder = new ThreadLocal < Connection > () {    public Connection initialValue() {        return DriverManager.getConnection(DB_URL);    }};public static Connection getConnection() {    return connectionHolder.get();}
Session管理
12345678910111213
private static final ThreadLocal threadSession = new ThreadLocal();public static Session getSession() throws InfrastructureException {    Session s = (Session) threadSession.get();    try {        if (s == null) {            s = getSessionFactory().openSession();            threadSession.set(s);        }    } catch (HibernateException ex) {        throw new InfrastructureException(ex);    }    return s;}
-------------- 本文结束 --------------
觉得文章不错就打赏一下哟~~~

打赏

liuchao 微信支付

微信支付

liuchao 支付宝

支付宝

锁优化技术

秒杀系统的设计

if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {

window.Promise = null;

}

// Popup Window;

var isfetched = false;

var isXml = true;

// Search DB path;

var search_path = "search.xml";

if (search_path.length === 0) {

search_path = "search.xml";

} else if (/json$/i.test(search_path)) {

isXml = false;

}

var path = "/blog/" + search_path;

// monitor main search box;

var onPopupClose = function (e) {

$('.popup').hide();

$('#local-search-input').val('');

$('.search-result-list').remove();

$('#no-result').remove();

$(".local-search-pop-overlay").remove();

$('body').css('overflow', '');

}

function proceedsearch() {

$("body")

.append('

.css('overflow', 'hidden');

$('.search-popup-overlay').click(onPopupClose);

$('.popup').toggle();

var $localSearchInput = $('#local-search-input');

$localSearchInput.attr("autocapitalize", "none");

$localSearchInput.attr("autocorrect", "off");

$localSearchInput.focus();

}

// search function;

var searchFunc = function(path, search_id, content_id) {

'use strict';

// start loading animation

$("body")

.append('

' +

'

' +

'' +

'

' +

'

')

.css('overflow', 'hidden');

$("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

$.ajax({

url: path,

dataType: isXml ? "xml" : "json",

async: true,

success: function(res) {

// get the contents from search data

isfetched = true;

$('.popup').detach().appendTo('.header-inner');

var datas = isXml ? $("entry", res).map(function() {

return {

title: $("title", this).text(),

content: $("content",this).text(),

url: $("url" , this).text()

};

}).get() : res;

var input = document.getElementById(search_id);

var resultContent = document.getElementById(content_id);

var inputEventFunction = function() {

var searchText = input.value.trim().toLowerCase();

var keywords = searchText.split(/[\s\-]+/);

if (keywords.length > 1) {

keywords.push(searchText);

}

var resultItems = [];

if (searchText.length > 0) {

// perform local searching

datas.forEach(function(data) {

var isMatch = false;

var hitCount = 0;

var searchTextCount = 0;

var title = data.title.trim();

var titleInLowerCase = title.toLowerCase();

var content = data.content.trim().replace(/]+>/g,"");

var contentInLowerCase = content.toLowerCase();

var articleUrl = decodeURIComponent(data.url);

var indexOfTitle = [];

var indexOfContent = [];

// only match articles with not empty titles

if(title != '') {

keywords.forEach(function(keyword) {

function getIndexByWord(word, text, caseSensitive) {

var wordLen = word.length;

if (wordLen === 0) {

return [];

}

var startPosition = 0, position = [], index = [];

if (!caseSensitive) {

text = text.toLowerCase();

word = word.toLowerCase();

}

while ((position = text.indexOf(word, startPosition)) > -1) {

index.push({position: position, word: word});

startPosition = position + wordLen;

}

return index;

}

indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));

indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));

});

if (indexOfTitle.length > 0 || indexOfContent.length > 0) {

isMatch = true;

hitCount = indexOfTitle.length + indexOfContent.length;

}

}

// show search results

if (isMatch) {

// sort index by position of keyword

[indexOfTitle, indexOfContent].forEach(function (index) {

index.sort(function (itemLeft, itemRight) {

if (itemRight.position !== itemLeft.position) {

return itemRight.position - itemLeft.position;

} else {

return itemLeft.word.length - itemRight.word.length;

}

});

});

// merge hits into slices

function mergeIntoSlice(text, start, end, index) {

var item = index[index.length - 1];

var position = item.position;

var word = item.word;

var hits = [];

var searchTextCountInSlice = 0;

while (position + word.length <= end && index.length != 0) {

if (word === searchText) {

searchTextCountInSlice++;

}

hits.push({position: position, length: word.length});

var wordEnd = position + word.length;

// move to next position of hit

index.pop();

while (index.length != 0) {

item = index[index.length - 1];

position = item.position;

word = item.word;

if (wordEnd > position) {

index.pop();

} else {

break;

}

}

}

searchTextCount += searchTextCountInSlice;

return {

hits: hits,

start: start,

end: end,

searchTextCount: searchTextCountInSlice

};

}

var slicesOfTitle = [];

if (indexOfTitle.length != 0) {

slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));

}

var slicesOfContent = [];

while (indexOfContent.length != 0) {

var item = indexOfContent[indexOfContent.length - 1];

var position = item.position;

var word = item.word;

// cut out 100 characters

var start = position - 20;

var end = position + 80;

if(start < 0){

start = 0;

}

if (end < position + word.length) {

end = position + word.length;

}

if(end > content.length){

end = content.length;

}

slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));

}

// sort slices in content by search text's count and hits' count

slicesOfContent.sort(function (sliceLeft, sliceRight) {

if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {

return sliceRight.searchTextCount - sliceLeft.searchTextCount;

} else if (sliceLeft.hits.length !== sliceRight.hits.length) {

return sliceRight.hits.length - sliceLeft.hits.length;

} else {

return sliceLeft.start - sliceRight.start;

}

});

// select top N slices in content

var upperBound = parseInt('1');

if (upperBound >= 0) {

slicesOfContent = slicesOfContent.slice(0, upperBound);

}

// highlight title and content

function highlightKeyword(text, slice) {

var result = '';

var prevEnd = slice.start;

slice.hits.forEach(function (hit) {

result += text.substring(prevEnd, hit.position);

var end = hit.position + hit.length;

result += '' + text.substring(hit.position, end) + '';

prevEnd = end;

});

result += text.substring(prevEnd, slice.end);

return result;

}

var resultItem = '';

if (slicesOfTitle.length != 0) {

resultItem += "

" + highlightKeyword(title, slicesOfTitle[0]) + "";

} else {

resultItem += "

" + title + "";

}

slicesOfContent.forEach(function (slice) {

resultItem += "" +

"

" + highlightKeyword(content, slice) +

"...

" + "";

});

resultItem += "

";

resultItems.push({

item: resultItem,

searchTextCount: searchTextCount,

hitCount: hitCount,

id: resultItems.length

});

}

})

};

if (keywords.length === 1 && keywords[0] === "") {

resultContent.innerHTML = '

} else if (resultItems.length === 0) {

resultContent.innerHTML = '

} else {

resultItems.sort(function (resultLeft, resultRight) {

if (resultLeft.searchTextCount !== resultRight.searchTextCount) {

return resultRight.searchTextCount - resultLeft.searchTextCount;

} else if (resultLeft.hitCount !== resultRight.hitCount) {

return resultRight.hitCount - resultLeft.hitCount;

} else {

return resultRight.id - resultLeft.id;

}

});

var searchResultList = '

  • ';

resultItems.forEach(function (result) {

searchResultList += result.item;

})

searchResultList += "

";

resultContent.innerHTML = searchResultList;

}

}

if ('auto' === 'auto') {

input.addEventListener('input', inputEventFunction);

} else {

$('.search-icon').click(inputEventFunction);

input.addEventListener('keypress', function (event) {

if (event.keyCode === 13) {

inputEventFunction();

}

});

}

// remove loading animation

$(".local-search-pop-overlay").remove();

$('body').css('overflow', '');

proceedsearch();

}

});

}

// handle and trigger popup window;

$('.popup-trigger').click(function(e) {

e.stopPropagation();

if (isfetched === false) {

searchFunc(path, 'local-search-input', 'local-search-result');

} else {

proceedsearch();

};

});

$('.popup-btn-close').click(onPopupClose);

$('.popup').click(function(e){

e.stopPropagation();

});

$(document).on('keyup', function (event) {

var shouldDismissSearchPopup = event.which === 27 &&

$('.search-popup').is(':visible');

if (shouldDismissSearchPopup) {

onPopupClose();

}

});

!function(e,t,a){function n(){c(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"),o(),r()}function r(){for(var e=0;e

一键复制

编辑

Web IDE

原始数据

按行查看

历史

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值