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线程安全问题原因及解决办法的更多相关文章

  1. com/opensymphony/xwork2/spring/SpringObjectFactory.java:220:-1问题出现的原因及解决办法

    转自:https://blog.csdn.net/shinchan_/article/details/37818927 com/opensymphony/xwork2/spring/SpringObj ...

  2. Java链接Redis时出现 “ERR Client sent AUTH, but no password is set” 异常的原因及解决办法

    Java链接Redis时出现 "ERR Client sent AUTH, but no password is set" 异常的原因及解决办法 [错误提示] redis.clie ...

  3. SpringBoot整合Swagger2案例,以及报错:java.lang.NumberFormatException: For input string: ""原因和解决办法

    原文链接:https://blog.csdn.net/weixin_43724369/article/details/89341949 SpringBoot整合Swagger2案例 先说SpringB ...

  4. Java ConcurrentModificationException异常原因和解决方法

    Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...

  5. Java并发编程:Java ConcurrentModificationException异常原因和解决方法

    Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...

  6. 【转】Java ConcurrentModificationException异常原因和解决方法

    原文网址:http://www.cnblogs.com/dolphin0520/p/3933551.html Java ConcurrentModificationException异常原因和解决方法 ...

  7. (转)Java ConcurrentModificationException异常原因和解决方法

    转载自:http://www.cnblogs.com/dolphin0520/p/3933551.html 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会 ...

  8. Java ConcurrentModificationException异常原因和解决方法(转)

    摘自:http://www.cnblogs.com/dolphin0520/p/3933551.html#undefined 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同 ...

  9. 9、Java ConcurrentModificationException异常原因和解决方法

    Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...

随机推荐

  1. C#高性能Socket服务器IOCP实现

    引言我一直在探寻一个高性能的Socket客户端代码.以前,我使用Socket类写了一些基于传统异步编程模型的代码(BeginSend.BeginReceive,等等)也看过很多博客的知识,在linux ...

  2. hihoCoder hiho一下 第一周 #1032 : 最长回文子串 (Manacher)

    题意:给一个字符串,求最长回文子串的长度. 思路: (1)暴力穷举.O(n^3) -----绝对不行. 穷举所有可能的出现子串O(n^2),再判断是否回文O(n).就是O(n*n*n)了. (2)记录 ...

  3. cesium加载shp格式数据

    方法一: shp格式转换为GeoJson格式并加载 首先注意shp的坐标系,要转换为WGS84,使用arcgis或QGIS 工具:http://mapshaper.org/: 注意:export时,输 ...

  4. 【洛谷1967】货车运输(最大生成树+倍增LCA)

    点此看题面 大致题意: 有\(n\)个城市和\(m\)条道路,每条道路有一个限重.多组询问,每次询问从\(x\)到\(y\)的最大载重为多少. 一个贪心的想法 首先,让我们来贪心一波. 由于要求最大载 ...

  5. flutter 踩坑总结

    导入第三方库踩坑小结: (编译器:VsCode) ( 打算在学习中,使用flutter重新自己的项目,遇到比较特殊的坑,就先记录一下,持续更新中) 1.把第三方库 写入pubspec.yaml文件中 ...

  6. 漫谈 Clustering (番外篇): Dimensionality Reduction

    由于总是有各种各样的杂事,这个系列的文章竟然一下子拖了好几个月,(实际上其他的日志我也写得比较少),现在决定还是先把这篇降维的日志写完.我甚至都以及忘记了在这个系列中之前有没有讲过“特征”(featu ...

  7. 操作系统(1)_操作系统结构_李善平ppt

    cpu和内存之间通过地址总线.数据总线.控制总线连接.外部总线连接外部设备.下图有问题,内存和外设没有直接连接.同一组总线,CPU和内存连接的时候硬盘就不能和内存连接,否则有冲突,core和core之 ...

  8. 第22题:链表中倒数第k个结点

    题目描述 题目:输入一个链表,输出该链表中倒数第k个结点.为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点.例如一个链表有6个结点,从头结点开始它们的值依次是1.2.3.4.5 ...

  9. [牛客OI测试赛2]F假的数学游戏(斯特灵公式)

    题意 输入一个整数X,求一个整数N,使得N!恰好大于$X^X$. Sol 考试的时候只会$O(n)$求$N!$的前缀和啊. 不过最后的结论挺好玩的 $n! \approx \sqrt{2 \pi n} ...

  10. nginx修改nginx.conf配置可以https访问

    修改nginx.conf,参照如下更改配置server { listen 443; server_name abc.com; // 访问域名 ssl on; root /var/www/bjubi.c ...