Java 中线程安全问题
不好意思,一个国庆假期给我放的都不知道东西南北了,放松,很放松,差一点就弃更了,感谢那些催更的小伙伴们!
虽然没有更新,但是日常的学习还是有的,以后我尽量给大家分享一些通用知识,非技术。
但是本期还是要回归到之前的多前程的话题。已经说了线程和进程的区别、如何实现多线程、今天说一说线程中的安全问题。
首先明确一个概念,我们说线程安全是默认在多线程环境中,因为单线程中不存在线程安全问题。线程安全体现在多线程环境中程序的执行结果和单线程执行的结果一样。
那么多线程中会存在神马问题呢?举个例子来说,下面这一段代码中存在一个共享变量 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 中线程安全问题的更多相关文章
- java中线程安全问题
在java中单线程和多线程是什么意思,他们有什么区别,分别的作用是什么? 在一个程序中,这些独立运行的程序片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”.多线程处理一个常见的例 ...
- Java基础-线程安全问题汇总
Java基础-线程安全问题汇总 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.内存泄漏和内存溢出(out of memory)的区别 1>.什么是内存溢出 答:内存溢出指 ...
- java中线程分两种,守护线程和用户线程。
java中线程分为两种类型:用户线程和守护线程. 通过Thread.setDaemon(false)设置为用户线程: 通过Thread.setDaemon(true)设置为守护线程. 如果不设置次属性 ...
- java中线程机制
java中线程机制,一开始我们都用的单线程.现在接触到多线程了. 多线性首先要解决的问题是:创建线程,怎么创建线程的问题: 1.线程的创建: 四种常用的实现方法 1.继承Thread. Thread是 ...
- Java中线程的使用 (2)-多线程、线程优先级、线程睡眠、让步、阻塞
Java中线程的使用 (2)-多线程.线程优先级.线程睡眠.让步.阻塞 (一)多线程使用方法 说明:创建每个新的线程,一定要记得启动每个新的线程(调用.start()方法) class Xc3 ext ...
- Java中线程的实现:
Java中线程的实现: 一.线程简介: 实现的两种方式为: 1.Thread类 2.Runnable接口 都在java.lang中 都有共通的方法:public void run() 二.线程常用方法 ...
- JAVA中线程同步方法
JAVA中线程同步方法 1 wait方法: 该方法属于Object的方法,wait方法的作用是使得当前调用wait方法所在部分(代码块)的线程停止执行,并释放当前获得的调用wait所 ...
- 多线程(三) java中线程的简单使用
java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依 ...
- Java中线程池,你真的会用吗?
在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理. 在文中有这样一段描述: 可以通过Executors静态工厂构建线程池,但一般不建 ...
随机推荐
- Ubuntu 15.04 双击运行 *.sh、*.py文件
源 起 之前一直在Windows下用AndoridStudio,今天试了一下在Linux系统Ubuntu 15.04中配置Android Studio: 过程和Windws下差不多,但是最后没有生成桌 ...
- python scrapy 基本操作演示代码
# -*- coding: utf-8 -*- import scrapy # from quotetutorial.items import QuoteItem from quotetutorial ...
- UNIX环境高级编程 第11章 线程
使用C++调用pthread_cleanup_push( )时,下面的代码是无法编译通过的: pthread_cleanup_push(cleanup, "thread 1 first ha ...
- [转]双线性插值(Bilinear interpolation)
1,原理 在图像的仿射变换中,很多地方需要用到插值运算,常见的插值运算包括最邻近插值,双线性插值,双三次插值,兰索思插值等方法,OpenCV提供了很多方法,其中,双线性插值由于折中的插值效果和运算速度 ...
- Spring4笔记5--基于注解的DI(依赖注入)
基于注解的DI(依赖注入): 对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 Bean 实例.只需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解. < ...
- aarch64_a1
AGReader-1.2-16.fc26.aarch64.rpm 2017-02-14 07:01 50K fedora Mirroring Project ATpy-0.9.7-11.fc26.no ...
- 利用github pages五分钟建好个人网站+个人博客
笔者自己在建个人网站/个人博客的时候其实遇到了不少麻烦,但是都一一解决了,这里教给大家最简单的方式. 首先你需要一个GitHub账号,访问https://github.com创建新账号即可. 然后访问 ...
- java基础59 JavaScript运算符与控制流程语句(网页知识)
1.JavaScript运算符 1.1.加减乘除法 加法:+(加法,连接符,正数) true是1,false是0 减法:- 乘法:* 除法:/ 1.2.比较运算符 ...
- Java 泛型和类型安全的容器
使用java SE5之前的容器的一个主要问题就是编译器允许你向容器插入不正确的类型,例如: //: holding/ApplesAndOrangesWithoutGenerics.java // Si ...
- HDU 3068 最长回文(manacher模板题)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3068 题目大意:求字符串s中最长的回文子串 解题思路:manacher模板 代码 #include&l ...