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 ...
随机推荐
- ansible基本操作
ansible优点:redhat自带工具,可通过rpm或yum直接安装:客户端免安装:操作通过ssh验证操作:可以通过自定义hosts文件对可操作主机进行分类,方便批量操作 #ansible操作格式, ...
- mysql用户权限操作
mysql用户权限操作1.创建用户mysql -urootcreate database zabbix default charset utf8;grant all on zabbix.* to za ...
- c++ STL stack容器成员函数
这是后进先出的栈,成员函数比较简单,因为只能操作栈顶的元素.不提供清除什么的函数. 函数 描述 bool s.empty() 栈是否为空(即size=0).若空,返回true,否则,false. vo ...
- linux 命令——7 mv(转)
mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录. 1.命令格式: mv [选项] 源文件或目 ...
- codeforces 600D Area of Two Circles' Intersection
分相离,内含,想交三种情况讨论一下. 主要是精度和数据范围的问题,首先数据用long double,能用整型判断就不要用浮点型. 题目中所给的坐标,半径是整型的,出现卡浮点判断的情况还是比较少的. 最 ...
- Problem G: 圆周率
Problem G: 圆周率 Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 155 Solved: 99[Submit][Status][Web Bo ...
- java 如何使的float保留2位或者多位小数
方法1: float f = 34.232323; BigDecimal b = new BigDecimal(f); float f1 = b.set ...
- angular4 学习日志(一 依赖注入)
1.创建一个服务,为了好管理建一个名叫services的文件夹管理所有服务: ng g service services\person 2.在服务中定义一个person 类 : 3.在app.mdul ...
- ArrayList集合例题,商品库存管理(集合)
创建车库集合,存进车 public class demo1 { String pinpai; String c; int s; } import java.util.ArrayList; class ...
- Oracle 配置文件目录
Oracle 配置文件目录 ① 在oracle安装目录下,找D:\oracle\product\10.2.0\client_1\NETWORK\ADMIN中的tnsnames.ora文件,找到之后,配 ...