一、线程不安全的原因

线程在执行的过程中出现错误的主要原因有以下几种:

1、根本原因

导致线程不安全的所有原因中,最根本的原因是——抢占式执行。因为CPU字在进行线程调度的时候,是随机调度的,而且这是无法避免的一种原因。

2、代码结构

当多个线程同时修改同一个变量的时候,很容易产生线程的不安全。所以不可变对象是天然线程安全的,比如String。可以通过调整代码结构来避免这个问题,但是这种调整不是一定都能够适用的,这虽然是一个方案,但不具备普适性。

3、原子性

修改操作如果是原子的,出现问题的概率小,但如果试非原子的,出现问题的概率就非常大了。原子性就是不可拆分的基本单位。

比如代码在执行“++”操作时,会分为三个阶段,load、add、save三个原子操作,所以多线程对同一变量进行“++”操作时,会出现不安全的状态。

针对线程安全问题,最主要的解决手段就是从这个原子性入手,把非原子的操作,变成原子的。

4、内存可见性问题

上述的多个线程对同一个变量进行修改时,会出现不安全的问题,同样的,一个线程读,一个线程改的操作也存在安全问题,可能就造成脏读,读的结果不符合预期。

5、指令重排序

编译器在执行代码时,会检测代码的,会存在编译器自作主张在保证相同的逻辑情况下对代码进行优化和修改,从而加快程序执行的效率,这是发生在单个线程里面的。这就有可能出现安全问题。

上述分析的五个原因,只是比较典型的,并不是全部原因。一个代码究竟是不是线程安全的,需要具体问题具体分析。即使某个代码踩中了上面的某个原因或几个原因,但仍然有可能是线程安全的,反过来说,即便某个代码一个都没踩中,也有可能是不安全的。

二、避免线程出现问题

解决线程安全问题也有几种主要的方法,首先介绍一个从原子性入手来解决安全问题的操作——加锁。

1、synchronized

(1)案例简介

多个线程在进行同一变量修改时:

class Counter1{public int count;synchronized public void add(){count++;}
}
public class prastice {public static void main(String[] args) {Counter1 counter1 = new Counter1();Thread thread1 = new Thread(()->{for (int i = 0; i < 10000; i++) {counter1.add();}});Thread thread2 = new Thread(()->{for (int i = 0; i < 10000; i++) {counter1.add();}});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(counter1.count);}
}

如果不加synchronized的话,最终打印的结果基本每次都是小于20000的,因为两个线程在实际对count进行++操作的时候,流程如下图:

这只是无数多种可能中的一种情况,两个线程分别进行一次++操作后,count的值只增加了1,产生的原因就是++操作在这里不是原子性的,线程1、2在执行时,很有可能相互交叉,进而导致结果错误。但是一旦加上synchronized锁后:

一旦线程进入锁中,其他线程就无法再进入,直到上锁的线程结束执行,解锁后,方可进入。

(2)synchronized的使用方法

修饰方法:修饰普通方法时,关键字在public前后都可,锁对象是 this,也就是谁调用谁上锁。修饰静态方法时,锁对象是类对象。

修饰代码块:修饰代码块时,显式/手动指定锁对象。

对于构造方法来说,如果加锁,不能直接加在方法上,但是内部可以使用代码块的方法,来加锁。

(3)死锁

死锁就是表面意思线程卡住无法继续执行,出现死锁大概有以下三种情况:

1)一个线程一把锁,连续加锁两次,如果锁是不可重入的,就会造成死锁。而synchronized是可重入锁,所以不会出现这种情况的死锁。

2)两个线程两把锁,t1 和 t2 各自先针对A和B进行加锁操作,两者分别加锁完成之后,再尝试获取对方的锁,就会造成死锁。

public class prastic2 {public static void main(String[] args) {Object t1 = new Object();Object t2 = new Object();Thread  t3 = new Thread(()->{synchronized (t1){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("拿到t1,尝试拿t2");synchronized (t2){};};});Thread t4 = new Thread(()->{synchronized (t1){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("拿到了t2,尝试去拿t1");synchronized (t2){};};});t3.start();t4.start();}
}

3)多个线程多把锁,相当于上一条的一般情况。

解决死锁的核心思想就是让线程统一一个顺序,按照类似从大到小这种有序的状态来进行执行,比如2中,两个线程同时先去拿A,然后再去拿B就能够很好的解决死锁问题。

2、volatile

这个关键字和内存可见性有着密切的联系。内存可见性问题实际是一个线程对一个变量进行读取操作,同时另一个线程针对这个变量进行修改,此时读到的值不一定是修改后的值,有可能读线程没有感知到变量的变化。归根结底就是编译器在多线程环境下优化时产生了误判。此时,volatile这个关键字就可以发挥作用了。用volatile来修饰变量,来告诉编译器,这是一个易变的变量,不可以随意进行优化。

class Sign{volatile public boolean flag = false;
}public class prastic3 {public static void main(String[] args) {Sign sign = new Sign();Thread t1 = new Thread(()->{while(!sign.flag){}System.out.println("执行完毕");});Thread t2 = new Thread(()->{sign.flag = true;});t1.start();try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}t2.start();}
}

此处,如果flag变量不被volatile修饰,程序就会一直运行,while感知不到flag的变化。原因就是,执行到线程2的时候,while空跑了好多遍,flag一直是false,所以被默认为不变,不再从内存中读取flag的值,而是读取寄存器中不变的flag的值,等到线程2执行到修改flag变量后,修改掉了内存中flag的值,但是寄存器中的flag依旧为原来的值,所以while后感知的flag是没变的,一直循环跑。

3、wait和notify

某个线程调用wait方法,就会进入阻塞,(无论是通过哪个对象调用的wait的)此时就处于WAITING状态。如果不加任何参数,就一直等待,直到被他的搭档notify唤醒。

wait的三个操作:先释放内存,然后进行阻塞等待,最后收到通知后,重新尝试获取锁,并在获取锁后,继续往下执行。wait的操作需要搭配synchronized使用。

Object object = new Object();
synchronized (object){object.wait();
}
synchronized(object){object.notify();
}

object.wait();这里虽然wait阻塞在synchronized代码块里面,但实际上,这里的阻塞是释放了锁,此时其他的线程是可以获得到object这个对象的锁的,此时阻塞处于WAITING状态。负责通知wait的notify要和wait配对,而且notify只能唤醒在同一对象上的线程。同时要保证notify要在wait之后执行。

线程安全问题及解决方法相关推荐

  1. Python线程安全问题及解决方法

    Python线程安全问题及解决方法 Python多线程是通过threading模块来实现的. 参考: https://mp.csdn.net/postedit/91069618 一.多线程共享全局变量 ...

  2. Java非线程安全问题的解决方法

    非线程安全.非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改.值不同步的情况,进而影响程序的执行流程.下面用一个示例来学习一下如何解决非线程安全问题. 本案例模拟了多线 ...

  3. HashMap线程安全问题以及处理方法!

    一:HashMap为什么会有线程安全问题? 我们知道jdk1.7和jdk1.8中HashMap都是线程不安全的,那就具体讲一下为什么会线程不安全(两个方面). ①调用put方法 假如有两个线程A和B, ...

  4. aspx mysql 安全问题_ASP+access的安全问题及解决方法

    时 间:2009-08-11 08:38:09 作 者:摘 要:ASP+Access的安全问题及解决方法 正 文: 随着Internet的发展,Web技术日新月异.继通用网关接口(CGI)之后,&qu ...

  5. 13.8 线程的安全问题和解决方法

    package cn.chen.threadsecurity; /* 出现线程安全问题的原因:1.存在两个或两个以上的线程对象,而且线程之间共享一个资源.2.有多个语句操作了共享资源.java线程同步 ...

  6. SimpleDateFormat的线程安全问题及解决办法

    SimpleDateFormat是Java中的日期转换类,面试中也会经常问到为什么有线程安全问题,最近真的在运维项目中遇到这个问题,可能之前并发量很少没有发生,最近频繁出现才发现系统中很多处使用都有问 ...

  7. Java 线程安全问题及解决

    线程安全 我们先来说说线程安全问题是什么? 线程安全问题其实就是多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果.所以我们建议在没有使用解决方案的时候尽量只读不写 首先我们写了一个简单 ...

  8. Java多线程基础-6:线程安全问题及解决措施,synchronized关键字与volatile关键字

    线程安全问题是多线程编程中最典型的一类问题之一.如果多线程环境下代码运行的结果是符合我们预期的,即该结果正是在单线程环境中应该出现的结果,则说这个程序是线程安全的. 通俗来说,线程不安全指的就是某一代 ...

  9. java中线程死锁的解决方法_Java线程死锁实例及解决方法

    这篇文章主要介绍了Java线程死锁实例及解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.死锁的定义 所谓死锁是指多个线程因竞争资源而造成 ...

最新文章

  1. c语言编程:输入一个数看它是不是素数
  2. python图像边缘检测
  3. Failed to find byte code for java/util/function/BiConsumer
  4. 查询本机公网ip地址的方法
  5. 最新综述:作为体现具体化自然语言环境的文本世界
  6. GridView分页后RowCommand出错:索引超出范围
  7. Windows Server 2008 R2 域控DOS命令
  8. 轻松搞定RocketMQ入门 1
  9. NLP --- 命名体识别(NER)
  10. linux文件类型elf,ELF文件格式的三种类型
  11. 机器人论文(1)-下肢外骨骼的平衡与稳定性问题:系统综述
  12. ps4 优酷 html5,ps4-hen-vtx/index.html at master · xvortex/ps4-hen-vtx · GitHub
  13. 牛客网:牛牛玩平板(c++)
  14. 小程序webview应用实践
  15. KDF- key derivation function
  16. 使用Ubuntu自带Disks工具扩展(扩容 )Vmware中ubuntu 20.04的硬盘空间
  17. Python如何快速爬取淘宝MM呢?教你一招
  18. java给图片、word、ppt、excel、pdf添加水印
  19. typora上传图片小白教程
  20. 攻防世界-web xff_Referer

热门文章

  1. 一、什么是Nginx? Nginx的作用是什么?
  2. Repo:Deep Learning with Differential Privacy
  3. GIS实验之加权泰森多边形的应用
  4. html狙击瞄准特效,如何给视频加特效:制作电影中狙击场景效果 望远镜效果视频...
  5. Flask中 jsonify有什么作用?如何使用?
  6. canal - 数据同步工具
  7. python除法有余数时+1
  8. LaTeX之公式及编号
  9. video.js 视频直播前端展示
  10. Android 资源管理利器Resources和AssetManager介绍