零、基本概念
内存管理模型JMM(Java Memory Model):Java 内存模型来屏蔽掉各种硬件和操作系统的内存差异,达到跨平台的内存访问效果。
主内存:所有的线程所共享。
JMM规定了所有的变量都存储在主内存中,此处的主内存仅仅是虚拟机内存的一部分
工作内存:每个线程自己有一个,不共享。
线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的所有操作(读取、赋值),都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者之间的交互关系如下图:
在并发编程中一般要考虑三个基本概念:
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
(在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。)
举个栗子:一下操作哪些是原子操作
i = 0; --1
j = i ; --2
i++; --3
i = j + 1; --4
1—在Java中,对基本数据类型的变量和赋值操作都是原子性操作;
2—包含了两个操作:读取i,将i值赋值给j
3—包含了三个操作:读取i值、i + 1 、将+1结果赋值给i;
4—同三一样
有序性:即程序执行的顺序按照代码的先后顺序执行。
(Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性)
在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序它不会影响单线程的运行结果,
但是对多线程会有影响。
一、volatile
1.1 什么是volatile
volatile,Java语言提供的一种稍弱的同步机制,volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。当一个变量定义为 volatile 之后,将具备两种特性:
1.保证此变量对所有的线程的可见性
volatile 变量,JVM 保证了每次读变量都从主内存中读变量本身,跳过工作内存这一步。 非volatile 变量,每次读变量都从工作内存中读变量的副本 |
2.禁止指令重排序优化(有序性)
1.2 volatile的性能
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令。
1.3 volatile的使用条件
在某些场景下,因为不会引起线程的上下文切换,volatile相当于轻量级的synchronized,但使用volatile必须满足两个条件: 1、对变量的写操作不依赖当前值
2、该变量没有包含在具有其它变量的不变式中:下界总是小于或等于上界
|
1.4 volatile的使用场景
状态标志
可以在循环外部--其他线程--来终止循环,然而,使用 synchronized 块编写循环要比使用volatile 状态标志编写麻烦很多。由于 volatile 简化了编码,并且状态标志并不依赖于程序内任何其他状态,因此此处非常适合使用 volatile。 双重检查模式 (DCL)
|
与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。 如果严格遵循 volatile 的使用条件即变量真正独立于其他变量和自己以前的值 ,在某些情况下可以使用 volatile 代替 synchronized 来简化代码。然而,使用 volatile 的代码往往比使用锁的代码更加容易出错。 本文介绍了可以使用 volatile 代替 synchronized 的最常见的两种用例,其他的情况我们最好还是去使用synchronized 。 |
二、ThreadLocal
2.1、学习ThreadLocal一定要注意一下两点:
1、 ThreadLocal不是用来解决多线程的共享对象访问问题 2、 它也不是“本地线程”,ThreadLocal并不是一个Thread,而是Thread的局部变量 划重点:当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 |
2.2、ThreadLocal要解决的问题和使用场景
1、要解决的问题:保证缓存变量(实现线程内变量共享)的线程安全 假设有一个需求,需要缓存一个变量,能够线程内正常存取,且不受其他线程影响。 有人可能会想到静态map,但会有两个问题,其他线程的影响和访问并发。 2、使用场景 2.1、日志的打印(同一线程的日志一起打印,或者说一次事务的日志一起打印,因为一般默认一次事务都是由同一个线程执行的,将事务的日志保存在线程局部变量当中,当事务执行完成的时候统一打印) 2.2、数据路连接、事务,事务是和线程绑定起来的,Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection 2.3、调用SimpleDataFormat的工具类建议使用ThreadLocal 2.4、Session管理 |
2.3、ThreadLocal的方法
使用案例:
|
2.4、ThreadLocal的原理
ThreadLocal ThreadLocalMap : ThreadLocal类中的静态内部类 (实质存储变量的位置,在Thread中有类型为ThreadLocal.ThreadLocalMap类型的属性threadLocals) Entry : ThreadLocalMap中的静态内部类,若引用ThreadLocal(存储变量的结构)
|
这里,我没有贴上ThreadLocalMap中的方法源码。。。
2.5、共享问题的对比
线程间共享问题 | 线程内共享问题 | |
实质 | 多个线程共同操作同一份变量的安全问题 | 单个线程操作独自的变量的安全问题 |
变量区别 | 一份变量,多个线程访问(共享一份变量) | 一份变量,一个线程独有,线程内共享,不受其他线程影响 |
实现方法 | synchronized,Lock,volatile | ThreadLocal |
实现思路 | 以时间换空间 | 以空间换时间 |
三、synchronized
待续
四、Lock
待续
五、综合对比
待续