## 1、有序性
**程序执行的顺序按照代码中定义的先后顺序执行。**
涉及到了 Java 中的指令重排序问题,在程序运行过程中,编译器和处理器会对指定做重排序。但是 JMM (Java Memory Model)能够确保在不同的编译器和不同的处理器平台上,通过插入指定类型的 Memory Barrier 来禁止特定类型的编译器重排序和处理器重排序,为上层提供一致的内存可见性保证。
**指令重排序不会影响代码在单线程中的结果,但是多线程中并发执行的结果则是不可控的。**
比如线程 A:
```
context = getContext();
inited = true
```
线程 B:
```
while(!inited){
sleep();
}
doSomeThingWithContext(context);
```
如果是线程 A 发生了重排序,变成:
```
inited = true
context = getContext();
```
如果 inited 为 true ,这个时候 B 线程会跳出 while 循环,然后执行 `doSomeThingWithContext(context);`,但是 A 的 `context = getContext();`还没初始化完成,就会引起错误发生。
再比如:
```
语句 1:int a = 80;
语句 2:String name = ""
语句 3:a += 3;
语句 4:b = a*a;
```
如果保证了有序性,那么就严格按照 1、2、3、4执行代码。
如果没保证有序性,单线程中执行是没问题的,即使是按照 2、1、3、4执行,也是正确的结果,但是在单线程中绝对不会 按照 3、1、2、4等顺序执行的。
如果要防止重排序,需要使用volatile关键字,volatile关键字可以保证变量的操作是不会被重排序的,但是记住 volatile 只对基本数据类型有效,因为除了基本类型的之外的操作,基本都不是原子操作。
## 2、可见性
**当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。**
Java 的线程通信是通过共享内存的方式进行的,为了加快程序的执行速度,cpu 会把数据加载到 cpu 的缓存中去,操作完之后再刷新到内存中。
比如:
![](https://markdown-1258186581.cos.ap-shanghai.myqcloud.com/20190617182816.png)
实际上,线程操作的是自己的工作内存(CPU 缓存),不会直接操作主内存(指的是 RAM),如果 共享变量的初始值为 1;上面的线程 A 对 a 在自己的工作内存中做了 +1 操作,还没来得及刷新到主内存,这个时候 B 线程也对主内存中的 a 做了 +1 操作,然后线程 A 和 B 都把自己的工作内存中的 a 刷新到 主内存中,这个时候就不会是 a = 3;而是 a = 2,因为线程 A 和线程 B 操作的时候 a 都是 1;这就是线程可见性带来的问题。
## 3、原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
> 一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:
1. 从账户A减去1000元,
2. 往账户B加上1000元。
这2个操作必须要具备原子性才能保证不出现一些意外的问题。
我们操作数据也是如此,比如i++;其中就包括三步,
1. 读取 i 的值
2. i 的值加 1
3. 将加 1 以后的值赋值给 i
这行代码在java中是不具备原子性的,这三步任何一步发生了错误,就会导致最后结果的错误,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
- Java 面试题
- String、StringBuffer、StringBuilder 的区别?
- Java 中的四种引用
- 接口和抽象类的本质区别
- 集合框架
- 集合概述
- ArrayList 源码分析
- LinkedList 源码分析
- HashMap 源码分析
- LinkedHashMap 源码分析
- Android提供的 LruCache 的分析
- LinkedList 和 ArrayList 的区别
- 多线程
- 实现多线程的几种方式
- 线程的几种状态
- Thread 的 start() 和 run() 的区别
- sleep() 、yield() 和 wait() 的区别 ?
- notify() 和 notifyAll() 的区别?
- 保证线程安全的方式有哪几种?
- Synchronized 关键字
- volatile 和 synchronized 的区别?
- 如何正确的终止一个线程?
- ThreadLocal 原理分析
- 线程池
- 多线程的三个特征
- 五种线程池,四种拒绝策略,三种阻塞队列
- 给定三个线程如何顺序执行完以后在主线程拿到执行结果
- Java 内存模型
- 判定可回收对象算法
- equals 与 == 操作符
- 类加载机制
- 类加载简单例子
- 算法
- 时间、空间复杂度
- 冒泡排序
- 快速排序
- 链表反转
- IO
- 泛型
- Kolin 面试题
- Android 面试题
- Handler 线程间通信
- Message、MessageQueue、Looper、Handler 的对象关系
- Handler 使用
- Handler 源码分析
- HandlerThread
- AsyncTask
- IntentService
- 三方框架
- Rxjava
- rxjava 操作符有哪些
- 如何解决 RxJava 内存泄漏
- Rxjava 线程切换原理
- map和 flatmap 的区别
- Databinding引起的 java方法大于 65535 的问题
- Glide
- Glide 的缓存原理
- Glide 是如何和生命周期绑定的?不同的Context 有什么区别?
- Glide 、Picasso 、的区别,优劣势,如何选择?
- Jetpack
- 源码分析
- EventBus
- EventBus 源码分析
- RxBus 替代 EventBus
- OkHttp
- OkHttp 源码分析
- OkHttp 缓存分析
- RxPermission
- RxPermission 源码分析
- Retrofit
- create
- Retrofit 源码分析
- 优化
- 启动优化
- 布局优化
- 绘制优化
- 内存优化
- 屏幕适配
- 组件
- Activity
- Frgment
- Service
- ContentProvider
- BroadcastReceiver
- 进程间通信
- Binder机制和AIDL
- AILD 中的接口和普通的接口有什么区别
- in、out、inout 的区别
- Binder 为什么只需要拷贝一次
- 在android中,请简述jni的调用过程
- 生命周期
- Activity 生命周期
- Fragment 生命周期
- Service 生命周期
- onSaveInstanceState() 与 onRestoreIntanceState()
- 前沿技术
- 组件化
- 模块化
- 插件化
- 热更新
- UI - View
- Android 动画
- 事件分发机制
- WebView
- 系统相关
- 谈谈对 Context 的理解
- Android 版本
- App应用启动流程
- App 的打包
- App 的加固
- App 的安装
- Activity 启动流程
- ClassLoader
- Lru 算法加载 Bitmap 三级缓存原理
- Parcelable 和 Serializable 的区别
- Activity的启动流程
- 相关概念
- 网络相关
- Http
- Https
- Http 和 Https 的区别
- 为什么要进行三次握手和四次挥手?
- OkHttp使用Https访问服务器时信任所有证书
- 设计模式
- 单例模式
- 构建者模式
- 工厂模式
- 外观模式
- 代理模式