【Java并发】线程安全性

栏目: Java · 发布时间: 6年前

内容简介:定义:当多个线程访问某个类时,不管运行时环境采用线程安全性主要体现在三个方面:原子性、可见性、有序性:原子性在 JDK 中主要由两个方面体现出来:

线程安全性

定义:当多个线程访问某个类时,不管运行时环境采用 何种调度方式 或者这些线程将如何交替执行,并且在主调代码中 不需要任何额外的同步或协同 ,这个类都能表现出 正确的行为 ,那么就称这个类是线程安全的。

线程安全性主要体现在三个方面:原子性、可见性、有序性:

  • 原子性 :提供了互斥访问,同一时刻只能有一个线程来对它进行操作
  • 可见性 :一个线程对主内存的修改可以及时地被其他线程观察到
  • 有序性 :一个线程观察其他线程中的指令执行顺序,由于指令重 排序 的存在,该观察结果一般杂乱无序

原子性

原子性在 JDK 中主要由两个方面体现出来:

Atomic

一个是 JDK 中已经提供好的 Atomic 包,它们均使用了 CAS 完成线程的原子性操作(详见 【Java并发】浅析 AtomicLong & LongAdder )。

另一个是使用锁的机制来处理线程之间的原子性。锁主要包括:synchronized、lock。

synchronized

依赖于 JVM 去实现锁,因此在这个关键字作用对象的作用范围内,都是同一时刻只能有一个线程对其进行操作的。synchronized 是 Java 中的一个关键字,是一种同步锁。它可以修饰的对象主要有四种:

  • 修饰代码块:大括号括起来的代码,作用于 调用的对象
  • 修饰方法:整个方法,作用于 调用的对象
  • 修饰静态方法:整个静态方法,作用于 所有对象
  • 修饰类:括号括起来的部分,作用于 所有对象

注意:如果当前类是一个父类,子类调用父类的被 synchronized 修饰的方法,不会携带 synchronized 属性,因为 synchronized 不属于方法声明的一部分。

Lock

首先要说明的就是 Lock,通过查看 Lock 的源码可知,Lock 是一个接口。ReentrantLock 是唯一实现了 Lock 接口的类,意思是“可重入锁”,并且 ReentrantLock 提供了更多的方法。

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

锁的分类

  • 可重入锁

    • synchronized / ReentrantLock
  • 可中断锁

    • synchronized 不可中断, Lock 可中断
  • 公平锁

    • synchronized 非公平锁
    • ReentrantLockReentrantReadWriteLock 默认情况下非公平锁,可设置为公平锁
  • 读写锁

    • ReadWriteLock / ReentrantReadWriteLock

可见性

导致共享变量在线程间不可见的原因:

  • 线程交叉执行
  • 重排序结合线程交叉执行
  • 共享变量更新后的值没有在工作内存与主存间及时更新

JVM 对于可见性,提供了 synchronized 和 volatile:

synchronized

JMM 关于 synchronized 的两条规定:

  • 线程解锁前,必须把共享变量的最新值刷新到主内存
  • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意: 加锁与解锁是同一把锁

volatile

volatile 的方式是:通过加入 内存屏障禁止重排序 优化来实现。

  • 对 volatile 变量写操作时,会在写操作后加入一条 store 屏障指令,将本地内存中的共享变量值刷新到主内存。
  • 对 volatile 变量读操作时,会在读操作前加入一条 load 屏障指令,从主内存中读取共享变量。
  • volatile的屏障操作都是 cpu 级别的;适合状态验证,不适合累加值,volatile关键字不具有原子性。
  • 适合状态验证,不适合累加值,volatile关键字不具有原子性

有序性

Java 内存模型中,允许编译器和处理器对指令进行 重排序 ,但是重排序过程不会影响到 单线程 程序的执行,却会影响到多线程并发执行的正确性。而 Java 提供了 volatile、synchronized、Lock ,它们可以用来保证有序性。

另外,Java 内存模型具备一些先天的有序性,即不需要任何手段就能得到保证的有序性。通常被我们称为happens-before 原则(先行发生原则)。如果两个线程的执行顺序无法从 happens-before 原则推导出来,那么就不能保证它们的有序性,虚拟机就可以对它们进行重排序。

【以下规则摘抄自 《深入理解Java虚拟机》

  • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
  • 锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
  • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作(重要)
  • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行

思维导图

【Java并发】线程安全性

笔记整理自: 【IMOOC】Java并发编程与高并发解决方案


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Four

The Four

Scott Galloway / Portfolio / 2017-10-3 / USD 28.00

NEW YORK TIMES BESTSELLER USA TODAY BESTSELLER Amazon, Apple, Facebook, and Google are the four most influential companies on the planet. Just about everyone thinks they know how they got there.......一起来看看 《The Four》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换