java线程安全问题原因及解决办法
1.为什么会出现线程安全问题
计算机系统资源分配的单位为进程,同一个进程中允许多个线程并发执行,并且多个线程会共享进程范围内的资源:例如内存地址。当多个线程并发访问同一个内存地址并且内存地址保存的值是可变的时候可能会发生线程安全问题,因此需要内存数据共享机制来保证线程安全问题。
对应到java服务来说,在虚拟中的共享内存地址是java的堆内存,比如以下程序中线程安全问题:
public class ThreadUnsafeDemo {
private static final ExecutorService EXECUTOR_SERVICE;
static {
EXECUTOR_SERVICE = new ThreadPoolExecutor(100, 100, 1000 * 10,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(100), new ThreadFactory() {
private AtomicLong atomicLong = new AtomicLong(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Thread-Safe-Thread-" + atomicLong.getAndIncrement());
}
});
}
public static void main(String[] args) throws Exception {
Map<String, Integer> params = new HashMap<>();
List<Future> futureList = new ArrayList<>(100);
for (int i = 0; i < 100; i++) {
futureList.add(EXECUTOR_SERVICE.submit(new CacheOpTask(params)));
}
for (Future future : futureList) {
System.out.println("Future result:" + future.get());
}
System.out.println(params);
}
private static class CacheOpTask implements Callable<Integer> {
private Map<String, Integer> params;
CacheOpTask(Map<String, Integer> params) {
this.params = params;
}
@Override
public Integer call() {
for (int i = 0; i < 100; i++) {
int count = params.getOrDefault("count", 0);
params.put("count", ++count);
}
return params.get("count");
}
}
}
创建100个task,每个task对map中的元素累加100此,程序执行结果为:
{count=9846}
而预期的正确结果为:
{count=10000}
至于出现这种问题的原因,下面会具体分析。
判断是否有线程安全性的一个原则是:
是否有多线程访问可变的共享变量
2.多线程的优势
发挥多处理器的强大能力,提高效率和程序吞吐量
3.并发带来的风险
使用并发程序带来的主要风险有以下三种:
3.1.安全性问题:
竞态条件:由于不恰当的执行时序而出现不正确的结果
对于1中的线程安全的例子就是由于竞态条件导致的最终结果与预期结果不一致。关键代码块如下:
int count = params.getOrDefault("count", 0);
params.put("count", ++count);
当多个线程同时取的count的值的时候,每个线程计算之后,在写入到count,这时候会出现多个线程值被覆盖的情况,最终导致结果不正确。
如下图所示:
3.2解决此类问题的几种方法
1.使用同步机制限制变量的访问:锁
比如:
synchronized (LOCK) {
int count = params.getOrDefault("count", 0);
params.put("count", ++count);
}
2.将变量设置为不可变
即将共享变量设置为final
3.不在线程之间共享此变量ThreadLocal
编程的原则:首先编写正确的代码,然后在实现性能的提升
无状态的类一定是线程安全的
3.3 内置锁
内置锁:同步代码块( synchronized (this) {})
进入代码块前需要获取锁,会有性能问题。内置锁是可重入锁,之所以每个对象都有一个内置锁,是为了避免显示的创建锁对象
常见的加锁约定:将所有的可变状态都封装在对象内部,并使用内置锁对所有访问可变状态的代码进行同步。例如:Vector等
同步的另一个功能:内存可见性,类似于volatile
非volatile的64位变量double、long:
JVM允许对64位的操作分解为两次32位的两次操作,可变64位变量必须用volatile或者锁来保护
加锁的含义不仅在于互斥行为,还包括内存可见性,为了所有线程都可以看到共享变量的最新值,所有线程应该使用同一个锁
原则:
除非需要跟高的可见性,否则应该将所有的域都声明为私有的,除非需要某个域是可变的,否则应该讲所有的域生命为final的
2.活跃性问题
线程活跃性问题主要是由于加锁不正确导致的线程一直处于等待获取锁的状态,比如以下程序:
public class DeadLock {
private static final Object[] LOCK_ARRAY;
static {
LOCK_ARRAY = new Object[2];
LOCK_ARRAY[0] = new Object();
LOCK_ARRAY[1] = new Object();
}
public static void main(String[] args) throws Exception {
TaskOne taskOne = new TaskOne();
taskOne.start();
TaskTwo taskTwo = new TaskTwo();
taskTwo.start();
System.out.println("finished");
}
private static class TaskOne extends Thread {
@Override
public void run(){
synchronized (LOCK_ARRAY[0]) {
try {
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println("Get LOCK-0");
synchronized (LOCK_ARRAY[1]) {
System.out.println("Get LOCK-1");
}
}
}
}
private static class TaskTwo extends Thread {
@Override
public void run() {
synchronized (LOCK_ARRAY[1]) {
try {
Thread.sleep(1000 * 3);
} catch (Exception e) {
}
System.out.println("Get LOCK-1");
synchronized (LOCK_ARRAY[0]) {
System.out.println("Get LOCK-0");
}
}
}
}
}
在两个线程持有一个锁,并在在锁没有释放之前,互相等待对方持有的锁,这时候会造成两个线程会一直等待,从而产生死锁。在我们使用锁的时候应该考虑持有锁的时长,特别是在网络I/O的时候。
在使用锁的时候要尽量避免以上情况,从而避免产生死锁
3.性能问题
在使用多线程执行程序的时候,在线程间的切换以及线程的调度也会消耗CPU的性能。
java线程安全问题原因及解决办法的更多相关文章
- com/opensymphony/xwork2/spring/SpringObjectFactory.java:220:-1问题出现的原因及解决办法
转自:https://blog.csdn.net/shinchan_/article/details/37818927 com/opensymphony/xwork2/spring/SpringObj ...
- Java链接Redis时出现 “ERR Client sent AUTH, but no password is set” 异常的原因及解决办法
Java链接Redis时出现 "ERR Client sent AUTH, but no password is set" 异常的原因及解决办法 [错误提示] redis.clie ...
- SpringBoot整合Swagger2案例,以及报错:java.lang.NumberFormatException: For input string: ""原因和解决办法
原文链接:https://blog.csdn.net/weixin_43724369/article/details/89341949 SpringBoot整合Swagger2案例 先说SpringB ...
- Java ConcurrentModificationException异常原因和解决方法
Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...
- Java并发编程:Java ConcurrentModificationException异常原因和解决方法
Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...
- 【转】Java ConcurrentModificationException异常原因和解决方法
原文网址:http://www.cnblogs.com/dolphin0520/p/3933551.html Java ConcurrentModificationException异常原因和解决方法 ...
- (转)Java ConcurrentModificationException异常原因和解决方法
转载自:http://www.cnblogs.com/dolphin0520/p/3933551.html 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会 ...
- Java ConcurrentModificationException异常原因和解决方法(转)
摘自:http://www.cnblogs.com/dolphin0520/p/3933551.html#undefined 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同 ...
- 9、Java ConcurrentModificationException异常原因和解决方法
Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...
随机推荐
- 24个节点测试Linux VPS/服务器速度一键脚本使用 附服务器配置
对于大部分网友而言,我们是希望购买的VPS.服务器既便宜也稳定,甚至还能提供更好的优质服务.这样的商家有没有呢?回答是基本没有.但是,只要我们购买的VPS在稳定性 和速度上对比同类的商家差不多,或者自 ...
- mybatis-关联关系2
关系关系主要有一对一,一对多,多对多,往往多对多都是通过俩个一对多来完成的 实例项目还是之前的,只是增加了一个年级实体类 1.创建年级实体类:---年级中有学生的集合 package com.java ...
- codeforces 1114C
题目连接 : https://codeforces.com/contest/1114/problem/C 题目大意:给一个整数n(1e18>=n>=0),和一个整数k(1e12>=k ...
- Java后台工程师的3次面试
第一次面试 我面的是一个中小公司,在BOSS直聘上面找的,去之前看了看关于Java的一些基础知识,在牛客网上面看的,也做了一下牛客网的题目.然后跟HR约了一个时间就去面试了.因为第一次面试,一点经验都 ...
- C++unsigned char和char区别
char和unsigned charchar与unsigned char都是一个字节8bit,唯一的区别是,char的最高位为符号位,因此char能表示-128~127, unsigned char( ...
- java基础编程——二维数组中的查找
题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数 ...
- vue使用animate.css类库实现动画
首先安装animate.css类库 cnpm install animate.css –save 然后在vue的script文件中引用 import $ from '../assets/js/jque ...
- 九、Linux 磁盘管理
Linux 磁盘管理 Linux磁盘管理好坏直接关系到整个系统的性能问题. Linux磁盘管理常用三个命令为df.du和fdisk. df:列出文件系统的整体磁盘使用量 du:检查磁盘空间使用量 fd ...
- Linux监控二之cacti简单安装部署
目录 cacti简单部署 1 环境依赖包部署 1 1. cacti中文版0.8e搭建 2 2. cacti安装向导 url:http://192.168.200.243/ ...
- 【函数应用】PHP中关于URL的函数处理
一,函数介绍 1.解析HTTP头信息:get_header() array get_headers ( string 目标URL [, int $format = 0 [如果将可选的 format 参 ...