不好意思,一个国庆假期给我放的都不知道东西南北了,放松,很放松,差一点就弃更了,感谢那些催更的小伙伴们!

虽然没有更新,但是日常的学习还是有的,以后我尽量给大家分享一些通用知识,非技术。

但是本期还是要回归到之前的多前程的话题。已经说了线程和进程的区别、如何实现多线程、今天说一说线程中的安全问题。

首先明确一个概念,我们说线程安全是默认在多线程环境中,因为单线程中不存在线程安全问题。线程安全体现在多线程环境中程序的执行结果和单线程执行的结果一样。

那么多线程中会存在神马问题呢?举个例子来说,下面这一段代码中存在一个共享变量 count 。当程序调用 add 方法的时候,我们无法确定 count 的值是多少,因为它可能已经被其它线程 add 了很多次,或是正在被 add……

public class Obj {
private int count; public int add() {
return ++count;
}
}

这种情况是坚决不允许的!除非你就是想计算 add 方法被调用的次数。那我们要保证同一时间只能有一个线程访问共享资源,这也就是在实现线程安全。

如何实现呢?最常见的方式是加锁和同步。

使用锁机制可以保证同一时刻只有一个线程可以拿到锁进入临界区,保证了上锁和释放锁之间的这段代码同一时刻只能被一个线程执行。

public void testLock () {
lock.lock();
try{
int j = i;
i = j + 1;
} finally {
lock.unlock();
}
}

与锁类似的是同步方法或者同步代码块。使用非静态同步方法时,锁住的是当前实例;使用静态同步方法时,锁住的是该类的 Class 对象;使用静态代码块时,锁住的是 synchronized 关键字后面括号内的对象。

public void testLock () {
synchronized (anyObject){
int j = i;
i = j + 1;
}
}

无论使用锁还是 synchronized,本质都是一样,通过锁或同步来实现资源的排它性,从而实际目标代码段同一时间只会被一个线程执行,进而保证了目标代码段的原子性。这是一种以牺牲性能为代价的方法。

除了上面的两种方式还有一起其它的方法,比方说使用关键字 volatile 来修饰共享变量,或是保证每一步操作的原子性,但是在实际开发中不建议使用这些方式来保证线程的安全。

那我们来看一看,保证线程安全到底是在保证什么?底层的逻辑又是什么呢?

线程安全的三大特性,原子性、可见性、有序性。

原子性:类似于数据库中说的原子性,不可分割的操纵。这里说的就是对临界区代码的原子性操作,保证了线程的安全。加锁和同步都是在实现原子性。

可见性:当一个线程修改了变量后立即同步到主存中,让其它线程知道这个变量已经被改变了。就不会读取到过时的数据。

Java 提供了 volatile 关键字来保证可见性。当使用 volatile 修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它线程缓存中对该变量的缓存设置成无效,因此其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。

有序性:在程序的执行顺序和代码的编写顺序一样。这就是有序性,但是Java虚拟机为了优化,有一种重排序的机制,也就是说代码的执行顺序不一定是语句的顺序不一致,这在单线程中不会存在问题,JVM 会保证执行结果的正确。然而在多线程中,就会出现问题。

Java 中可通过 volatile 在一定程序上保证有序性,另外还可以通过 synchronized 和锁来保证有序性。

synchronized 和锁保证有序性的原理和保证原子性一样,都是通过保证同一时间只会有一个线程执行目标代码段来实现的。

JVM 为了优化执行顺序,提高性能。设计了重排序机制,然而这个机制在多线程中可能会带来问题,为了解决这个问题,我们可以通过代码保证有序性。这还不够, JVM 本身存在一个机制 happens-before(先行执行)来保证程序执行顺序。其余的代码就随 JVM 怎么弄了,目的提高性能。

先行执行机制中定义了好几条规则,拿出几条参观一下,你会感觉,呦,原来你认为很自然的事情是被这个规则所定义的!

1 被 volatile 修饰的写操作先发生于后面对该变量的读操作。

2 一个线程内,按照代码顺序执行。

3 Thread 对象的 start() 方法先发生于此线程的其它动作。

4 一个对象构造先于它的 finalize 发生。

……

总结一下,线程安全问题发生在多线程环境下对共享变量的操作中,为了防止出现数据状态不一致的情况,我们可以不在多线程中共享变量、将变量设置为 volatile 、使用同步或锁。其实也是在保证线程的特性不受破坏……

Java 中线程安全问题的更多相关文章

  1. java中线程安全问题

    在java中单线程和多线程是什么意思,他们有什么区别,分别的作用是什么? 在一个程序中,这些独立运行的程序片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”.多线程处理一个常见的例 ...

  2. Java基础-线程安全问题汇总

    Java基础-线程安全问题汇总 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.内存泄漏和内存溢出(out of memory)的区别 1>.什么是内存溢出 答:内存溢出指 ...

  3. java中线程分两种,守护线程和用户线程。

    java中线程分为两种类型:用户线程和守护线程. 通过Thread.setDaemon(false)设置为用户线程: 通过Thread.setDaemon(true)设置为守护线程. 如果不设置次属性 ...

  4. java中线程机制

    java中线程机制,一开始我们都用的单线程.现在接触到多线程了. 多线性首先要解决的问题是:创建线程,怎么创建线程的问题: 1.线程的创建: 四种常用的实现方法 1.继承Thread. Thread是 ...

  5. Java中线程的使用 (2)-多线程、线程优先级、线程睡眠、让步、阻塞

    Java中线程的使用 (2)-多线程.线程优先级.线程睡眠.让步.阻塞 (一)多线程使用方法 说明:创建每个新的线程,一定要记得启动每个新的线程(调用.start()方法) class Xc3 ext ...

  6. Java中线程的实现:

    Java中线程的实现: 一.线程简介: 实现的两种方式为: 1.Thread类 2.Runnable接口 都在java.lang中 都有共通的方法:public void run() 二.线程常用方法 ...

  7. JAVA中线程同步方法

    JAVA中线程同步方法 1  wait方法:         该方法属于Object的方法,wait方法的作用是使得当前调用wait方法所在部分(代码块)的线程停止执行,并释放当前获得的调用wait所 ...

  8. 多线程(三) java中线程的简单使用

    java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依 ...

  9. Java中线程池,你真的会用吗?

    在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理. 在文中有这样一段描述: 可以通过Executors静态工厂构建线程池,但一般不建 ...

随机推荐

  1. OAuth2:Authorization Flows

    Ref:http://www.dannysite.com/blog/176/ OAuth2.0协议定义了用于获得授权的四种主要授权类型. 1. Client Credentials 一种基于APP的密 ...

  2. java8 write file 写文件

    1.用BufferedWriter写入文件 //Get the file reference Path path = Paths.get("c:/output.txt"); //U ...

  3. VS2010 项目属性的默认包含路径设置方法

    VS2010 项目属性的默认包含路径设置方法 分类: c++小技巧2014-01-10 10:16 1358人阅读 评论(0) 收藏 举报 c++ 有两种方法可以设置vs2010的默认包含路径 方法一 ...

  4. [转]Ubuntu下ROS开发环境搭建(QT+ros_qtc_plugin)

    ROS与C++入门教程-搭建开发环境(QT+ros_qtc_plugin) PS : 在“安装ros_qtc_plugin插件”这一步中,原文提到“ Ubuntu 14.04使用apt-get方式安装 ...

  5. javaScript书写规范

    命名规范. 常量名    全部大写并单词间用下划线分隔    如:CSS_BTN_CLOSE.TXT_LOADING对象的属性或方法名    小驼峰式(little camel-case)    如: ...

  6. vue引入jquery的方法

    1.局部引入 通过命令下载jquery   npm install jquery --save-dev 在需要引入jquery的组件中通过import $ from 'jquery'引入即可 2.全局 ...

  7. Java Dom对XML的解析和修改操作

    与Dom4J和JDom对XML的操作类似,JDK提供的JavaDom解析器用起来一样方便,在解析XML方面Java DOM甚至更甚前两者一筹!其不足之处在于对XML的增删改比较繁琐,特开篇介绍... ...

  8. flask基础之蓝图的使用(七)

    前言 关于蓝图是什么?或为什么使用蓝图的详细介绍,官方文档讲的很详细,不再赘述.简单来说,在大型的应用中,我们不想视图函数显得杂乱无章,难以维护,将众多的视图函数按照Api的设计规则进行切割是一个好方 ...

  9. MySQL字符集 GBK、GB2312、UTF8区别 解决 MYSQL中文乱码问题 收藏 MySQL中涉及的几个字符集

    MySQL中涉及的几个字符集 character-set-server/default-character-set:服务器字符集,默认情况下所采用的.character-set-database:数据 ...

  10. 【hihocoder1251】Today is a rainy day

    #include<bits/stdc++.h> ; ; const int inf=0x3f3f3f3f; using namespace std; char s1[N],s2[N]; ] ...