线程安全&Java内存模型

Java内存模型

Java内存模型(JMM)主要目标是定义多线程的状况下线程访问变量的规则java

JMM规定线程之间的共享变量存储在主内存中,每一个线程都有一个本地内存(工做内存),本地内存存储了共享变量的副本。安全

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTYwOTIxMTgyMzM3OTA0

关于线程安全

  • 什么是线程安全问题?多线程

    多个线程同时共享同一个全局变量的操做时候,可能会受到其余线程的干扰,致使数据脏读。(数据一致性问题)性能

  • 如何解决线程安全问题?线程

    核心思想:在同一时刻,只能有一个线程执行。对象

    经过加锁使线程更加安全,也使程序的执行效率更低。blog

  • 衡量线程安全的3个要素:排序

    • 原子性:一个操做或者多个操做要么所有执行,要么都不执行
    • 可见性:多个线程访问同一变量,一个线程修改了变量的值,其余线程能够当即看到修改的值
    • 有序性:程序按照代码的顺序前后执行(与指令重排有关)

Volatile关键字

volatile是一种轻量级的同步机制,能够保证可见性【及时将修改的变量刷新到主内存中】,但不能保证原子性,而且禁止重排序。递归

volatile在多线程下的适用场景:一写多读事务

volatile如何保证内存可见性?

当一个线程对volatile修饰的变量进行写操做时,该线程中的本地内存的变量会被马上刷新到主内存中。

当一个线程对volatile修饰的变量进行读操做时,该线程直接读取主内存的变量。

volatile可否保证线程安全?

不能,保证线程安全须要同时具有原子性,可见性和有序性。而volatile只能保证可见性和有序性,没法保证原子性。

Synchronized关键字

核心思想:在多线程执行同一个方法时,只有获取到锁,才能进入方法里面执行

使用方式:

  • 修饰一个类:其做用的范围是synchronized后面括号括起来的部分,做用的对象是这个类的全部对象
  • 修饰一个方法:被修饰的方法称为同步方法,其做用的范围是整个方法,做用的对象是调用这个方法的对象
  • 修饰一个静态的方法:其做用的范围是整个方法,做用的对象是这个类的全部对象
  • 修饰一个代码块:被修饰的代码块称为同步语句块,其做用范围是大括号{}括起来的代码块,做用的对象是调用这个代码块的对象

锁的分类

轻量级锁&重量级锁

轻量级锁:手动上锁解锁,扩展性强。表明:Lock

重量级锁:自动上锁解锁,封装程度高。表明:Synchronized

可重入锁&不可重入锁

可重入锁(递归锁):当一个线程已经获取到锁后,再次请求该锁,就可直接获取。(锁的传递,锁的嵌套)表明:Synchronized,Lock

锁的可重入性避免了大部分死锁状况的产生

不可重入锁:不具有传递性

读写锁

ReentrantReadWriteLock

相对Synchronized效率更高,但在多线程状况下,只支持读读共存,不支持读写,写写。

乐观锁与悲观锁

乐观锁(适合多读场景)

  • 思想:认为不会发生线程冲突(本质上是没有锁的)

  • 执行流程,先读取数据,而后在更新前检查在读取至更新这段时间数据是否被修改

    • 未修改:直接更新数据
    • 已修改:从新读取,再次提交更新(或者放弃操做)

为何乐观锁适合多读场景?

乐观锁是一种更新前的检查机制,相对于悲观锁来讲在多读场景下能够减小锁的性能开销,对于多写场景,乐观锁会一直进入已修改,从新读取,再次提交的循环,反而带来更多的资源消耗。

悲观锁(适合多写场景)

  • 思想:认为必定会发生线程冲突

  • 执行流程:读取数据的时候上锁(其余用户没法读取),直到本次数据更新完成才会释放锁。在多写场景下,能保证较高的数据一致性。

【总的来讲,乐观锁回滚重试,悲观锁阻塞事务】

CAS无锁机制

原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference可保证线程安全,底层使用CAS无锁机制

CAS:Compare and Swap,比较再交换,属于乐观锁的一种

  • CAS原理

    CAS包含3个参数,CAS(V,E,N),V:主内存的变量值,E:本地内存修改前的值,N:本地内存修改后的值

    比较主内存的值和本地内存修改前的值是否一致,若一致,将修改后的值刷新到主内存,若不一致,当前线程放弃更新,将主内存数据刷新到本地内存,再次重试。

  • 优势:非阻塞,不会发生死锁状况,效率更高

  • 缺点:ABA问题(能够经过加入版本号来区分变量是否被修改)

相关文章
相关标签/搜索