ThreadLocal提供了线程专有对象,可以在整个线程生命周期中随时取用,极大地方便了一些逻辑的实现。
常见的ThreadLocal用法主要有两种:
保存线程上下文对象,避免多层级参数传递;
保存非线程安全对象,避免多线程并发调用。
这里,以PageHelper插件的源代码中的分页参数设置与使用为例说明。
设置分页参数代码:
/** 分页方法类 */public abstract class PageMethod { /** 本地分页 */ protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>(); /** 设置分页参数 */ protected static void setLocalPage(Page page) { LOCAL_PAGE.set(page); } /** 获取分页参数 */ public static <T> Page<T> getLocalPage() { return LOCAL_PAGE.get(); } /** 开始分页 */ public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page<E> page = new Page<E>(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); Page<E> oldPage = getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } setLocalPage(page); return page; } }
使用分页参数代码:
/** 虚辅助方言类 */public abstract class AbstractHelperDialect extends AbstractDialect implements Constant { /** 获取本地分页 */ public <T> Page<T> getLocalPage() { return PageHelper.getLocalPage(); } /** 获取分页SQL */ @Override public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) { String sql = boundSql.getSql(); Page page = getLocalPage(); String orderBy = page.getOrderBy(); if (StringUtil.isNotEmpty(orderBy)) { pageKey.update(orderBy); sql = OrderByParser.converToOrderBySql(sql, orderBy); } if (page.isOrderByOnly()) { return sql; } return getPageSql(sql, page, pageKey); } ... }
使用分页插件代码:
/** 查询用户函数 */public PageInfo<UserDO> queryUser(UserQuery userQuery, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<UserDO> userList = userDAO.queryUser(userQuery); PageInfo<UserDO> pageInfo = new PageInfo<>(userList); return pageInfo; }
如果要把分页参数通过函数参数逐级传给查询语句,除非修改MyBatis相关接口函数,否则是不可能实现的。
在写日期格式化工具函数时,首先想到的写法如下:
/** 日期模式 */private static final String DATE_PATTERN = "yyyy-MM-dd";/** 格式化日期函数 */public static String formatDate(Date date) { return new SimpleDateFormat(DATE_PATTERN).format(date); }
其中,每次调用都要初始化DateFormat导致性能较低,把DateFormat定义成常量后的写法如下:
/** 日期格式 */private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");/** 格式化日期函数 */public static String formatDate(Date date) { return DATE_FORMAT.format(date); }
由于SimpleDateFormat是非线程安全的,当多线程同时调用formatDate函数时,会导致返回结果与预期不一致。如果采用ThreadLocal定义线程专有对象,优化后的代码如下:
/** 本地日期格式 */private static final ThreadLocal<DateFormat> LOCAL_DATE_FORMAT = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };/** 格式化日期函数 */public static String formatDate(Date date) { return LOCAL_DATE_FORMAT.get().format(date); }
这是在没有线程安全的日期格式化工具类之前的实现方法。在JDK8以后,建议使用DateTimeFormatter代替SimpleDateFormat,因为SimpleDateFormat是线程不安全的,而DateTimeFormatter是线程安全的。当然,也可以采用第三方提供的线程安全日期格式化函数,比如apache的DateFormatUtils工具类。
注意:ThreadLocal有一定的内存泄露的风险,尽量在业务代码结束前调用remove函数进行数据清除。
以上是java如何使用ThreadLocal存储线程专有对象的详细内容。更多信息请关注PHP中文网其他相关文章!