Java并发--线程安全策略
1 不可变对象
用不可变对象保证线程安全,是相当于不让线程并发,逃避了并发。
不可变对象就是指一个类的实例化对象不可变。比如String类的实例
主要方法有:
将类声明为final
将所有成员声明为 private
对变量不提供 set 方法,将所有可变成员声明为 final,只能赋值一次,通过构造器初始化所有成员,进行深度拷贝
在 get 方法中不直接返回对象本身,而是克隆对象,并返回对象的拷贝。
final关键字
一个类的private方法会隐式的指定为final方法
final还可以修饰方法的参数,表示该参数在方法中使用时不可以修改。
这里需要注意的是,final修饰的引用数据类型中其内部数据还是可以被修改的,比如一个final修饰的map,有数据<1,3>,可以被修改为<1,2>,所以并不是线程安全的。需要进行解决。
Collections.unmodifiableXXX 系列方法,可以解决上述问题,它可以将输入的引用对象变为不可变对象,内部也是不可变的
Guava:ImmutableXXX系列方法也可以达到相同的效果
2 线程封闭
把对象封装到一个线程中,只有这一个线程可以看到这个对象,那么这个对象就不用考虑线程安全问题。
下面用例子演示ThreadLocal的使用:
参考博客:面试官再问你 ThreadLocal,你就这样“怼”回去!
ThreadLocal实现里使用Map来保存的,Key保存线程id,value保存数据。
假设一个常见的用法,也就是前端往后端传递数据,比如一个用户数据,可能需要从Controller传到Service甚至传到Dao层,多次传递容易出现问题。我们可以利用ThreadLocal + Filter的方式,将数据在交给Controller处理前就保存下来,需要使用的时候进行获取,使用完释放。具体代码:
首先是保存有ThreadLocal的一个类,用以数据的添加、获取和移除
public class RequestHolder {
private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>();
// 保存数据
public static void add(Long id) {
requestHolder.set(id);
} // 获取数据
public static Long getId() {
return requestHolder.get();
} // 移除资源
public static void remove() {
requestHolder.remove();
}
}然后是编写Filter和Inteceptor
@Slf4j
public class HttpFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException { } @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
log.info("Filter执行,添加线程id:{}到ThreadLocal,请求是{}", Thread.currentThread().getId(), request.getServletPath());
RequestHolder.add(Thread.currentThread().getId());
filterChain.doFilter(servletRequest, servletResponse);
} @Override
public void destroy() { }
}public class HttpInteceptor 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 {
log.info("afterCompletion执行");
RequestHolder.remove();
return;
}
}注册Filter和Inteceptor
@SpringBootApplication
public class ConcurrencyApplication extends WebMvcConfigurerAdapter { public static void main(String[] args) {
SpringApplication.run(ConcurrencyApplication.class, args);
} /**
* 将自己实现的Filter进行注册
* 需要添加Filter实现类,对应url等信息
* @return
*/
@Bean
public FilterRegistrationBean httpFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
// 添加实现类
filterRegistrationBean.setFilter(new HttpFilter());
// 添加url
filterRegistrationBean.addUrlPatterns("/threadLocal/*");
return filterRegistrationBean;
} @Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HttpInteceptor()).addPathPatterns("/**");
}
}最后编写测试所用Controller
@RestController
@RequestMapping("/threadLocal")
public class ThreadLocalController { @RequestMapping("/test")
public Long test() {
return RequestHolder.getId();
}
}
3 线程不安全类和写法
一些常见的线程不安全类及其解决方法
1)StringBuilder(线程不安全,单线程推荐使用,效率高) 和 StringBuffer(线程安全,多线程使用)
2)SimpleDateFormat 线程不安全,参考博客:https://blog.csdn.net/csdn_ds/article/details/72984646
使用jodaTime(线程安全)
3)ArrayList、HashSet、HashMap等集合是 线程不安全的
4 同步容器
对集合进行遍历操作是不要做删除操作
5 并发容器 J.U.C
上面提到的同步容器,可以一定程度上保证线程安全,但还是会出现线程不安全的情况,所以出现了并发容器
1)ArrayList ---> J.U.C中的 CopyOnWriteArrayList .该类在更新操作的时候会加锁,然后先复制一份原来的List,再添加元素,然后重新赋值回原来的。 适合于list元素少,读多于写
2)
3)
最新文章
- XMLHttpRequest对象用法
- plain framework 1 参考手册 入门指引之 许可协议
- 前端模板artTemplate,handlerbars的使用心得
- HDU 4358 Boring counting(莫队+DFS序+离散化)
- 使用percona-toolkit校验主从数据的一致性
- 机器学习中的范数规则化之(一)L0、L1与L2范数(转)
- php输出函数 var_dump, dump,print,print_r 区别
- Codeforces Round #278 (Div. 2)
- [转]将某个Qt4项目升级到Qt5遇到的问题
- [Swust OJ 566]--开N方数(牛顿切线法解高次方程)
- Linux下PS命令详解 (转)
- Linux 火狐浏览器安装Flash插入
- VB6之HTTP服务器的实现(二)
- [NOI2012]美食节(费用流)
- IIC基本概念和基本时序
- 转://oracle 重新编译用户无效对象
- AFN拿不到cookie,无法存储cookie
- TypeError: write() argument must be str, not bytes报错原因及Python3写入二进制文件方法
- HTTP梳理
- thinkphp生成的验证码不显示问题解决
热门文章
- 克隆CentOS 6.9 配置静态IP,重启网络服务时报错
- 洛谷P3807 【模板】卢卡斯定理_组合数学模板
- Spring Boot 项目学习 (一) 项目搭建
- WEBGL学习【十二】鼠标操作场景
- CentOS 笔记(六) 历史命令 自动补充
- Elasticsearch 入门 - Exploring Your Cluster
- java+selenium自动化遇到confirm弹窗,出现NoAlertPresentException: no alert open
- BA-siemens-点位类型表
- html 页面刷新
- Android中的WiFi P2P