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

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

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

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

那么多线程中会存在神马问题呢?举个例子来说,下面这一段代码中存在一个共享变量 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. Centos7系统中安装Nginx1.8.0

    Nginx的安装 tar -zxvf nginx-1.8.0.tar.gz cd nginx-1.8.0 ./configure make make install /usr/local/nginx/ ...

  2. ES6的优雅方法

    1.箭头函数 // ES5 var selected = allJobs.filter(function (job) { return job.isSelected(); }); // ES6 var ...

  3. numpy之ones,array,asarray

    from:http://blog.csdn.net/gobsd/article/details/56485177 numpy.ones() 废话少说直接上代码 >>> np.ones ...

  4. imperva-代理安装

    首先创建网关上面的监听端口

  5. mysql5.7半自动同步设置【转】

    mysql的主从复制主要有3种模式: a..主从同步复制:数据完整性好,但是性能消耗高 b.主从异步复制:性能消耗低,但是容易出现主从数据唯一性问题 c.主从半自动复制:介于上面两种之间.既能很好的保 ...

  6. 【Educational Codeforces Round28】

    咸鱼选手发现自己很久不做cf了,晚节不保. A.Curriculum Vitae 枚举一下间断点的位置. #include<bits/stdc++.h> using namespace s ...

  7. Prepare tasks for django project deployment.md

    As we know, there are some boring tasks while deploy Django project, like create db, do migrations a ...

  8. 读书笔记 effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)

    假设你正在操作一个Rectangle类.每个矩形可以通过左上角的点和右下角的点来表示.为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放 ...

  9. 二十、springboot之jpa开发@MappedSuperclass 注解说明

    @MappedSuperclass使用条件: 当我们进行开发项目时,我们经常会用到实体映射到数据库表的操作,此时我们经常会发现在我们需要映射的几个实体类中,有几个共同的属性,例如编号ID,创建者,创建 ...

  10. 字符串匹配算法之 kmp算法 (python版)

    字符串匹配算法之 kmp算法 (python版) 1.什么是KMP算法 KMP是三位大牛:D.E.Knuth.J.H.MorriT和V.R.Pratt同时发现的.其中第一位就是<计算机程序设计艺 ...