Java 语言基础
基本数据类型
8 种基本数据类型
什么是浮点型?
在计算机科学中, 浮点是一种对于实数的近似值数值表现法, 由一个有效数字( 即尾数)加上幂数来表示, 通常是乘以某个基数的整数次指数得到。以这种表示法表示的数值, 称为浮点数( floating-point number) 。
计算机使用浮点数运算的主因, 在于电脑使用二进位制的运算。例如:4÷ 2=2, 4 的二进制表示为 100、 2 的二进制表示为 010, 在二进制中, 相当于退一位数(100 -> 010)。
1 的二进制是 01, 1.0/2=0.5, 那么, 0.5 的二进制表示应该为(0.1), 以此类推, 0.25的二进制表示为 0.01, 所以, 并不是说所有的十进制小数都能准确的用二进制表示出来,如 0.1, 因此只能使用近似值的方式表达。
也就是说, , 十进制的小数在计算机中是由一个整数或定点数( 即尾数) 乘以某个基数( 计算机中通常是 2) 的整数次幂得到的, 这种表示方法类似于基数为 10 的科学计数法。
为什么不能用浮点型表示金额?
由于计算机中保存的小数其实是十进制的小数的近似值, 并不是准确值, 所以, 千万不要在代码中使用浮点数来表示金额等重要的指标。建议使用 BigDecimal 或者 Long( 单位为分) 来表示金额。
Java 中的关键字
transient
简单点说, 就是被 transient 修饰的成员变量, 在序列化的时候其值会被忽略, 在被反序列化后, transient 变量的值被设为初始值, 如 int 型的是 0, 对象型的是 null。
instanceof
instanceof 是 Java 的一个二元操作符, 类似于 ==, >, < 等操作符
volatile
volatile 的用法
volatile 通常被比喻成"轻量级的 synchronized", 也是 Java 并发编程中比较重要的一个关键字。和 synchronized 不同, volatile 是一个变量修饰符, 只能用来修饰变量。无法修饰方法及代码块等。
volatile 的用法比较简单, 只需要在声明一个可能被多线程同时访问的变量时, 使用volatile 修饰就可以了。
volatile 的原理
但是, 对于 volatile 变量, 当对 volatile 变量进行写操作的时候, JVM 会向处理器发送一条 lock 前缀的指令, 将这个缓存中的变量回写到系统主存中。
但是就算写回到内存, 如果其他处理器缓存的值还是旧的, 再执行计算操作就会有问题,所以在多处理器下, 为了保证各个处理器的缓存是一致的, 就会实现缓存一致性协议。
缓存一致性协议:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了, 当处理器发现自己缓存行对应的内存地址被修改, 就会将当前处理器的缓存行设置成无效状态, 当处理器要对这个数据进行修改操作的时候, 会强制重新从系统内存里把数据读到处理器缓存里
所以, 如果一个变量被 volatile 所修饰的话, 在每次数据变化之后, 其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议, 也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个 volatile 在并发编程中, 其值在多个缓存中是可见的
volatile 与可见性
可见性是指当多个线程访问同一个变量时, 一个线程修改了这个变量的值, 其他线程能够立即看得到修改的值。
前面的关于 volatile 的原理中介绍过了, Java 中的 volatile 关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存, 被其修饰的变量在每次是用之前都从主内存刷新。因此, 可以使用 volatile 来保证多线程操作时变量的可见性。
volatile 与有序性
我们在再有人问你 Java 内存模型是什么, 就把这篇文章发给他中分析过:除了引入了时间片以外, 由于处理器优化和指令重排等, CPU 还可能对输入代码进行乱序执行, 比如load->add->save 有可能被优化成 load->save->add 。这就是可能存在有序性问题。
而 volatile 除了可以保证数据的可见性之外, 还有一个强大的功能, 那就是他可以禁止指令重排优化等。
volatile 可以禁止指令重排, 这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被 volatile 修饰的变量的操作, 会严格按照代码顺序执行,load->add->save 的执行顺序就是:load、 add、 save。
volatile 与原子性
原子性是指一个操作是不可中断的, 要全部执行完成, 要不就都不执行。
我们在 Java 的并发编程中的多线程问题到底是怎么回事儿?中分析过:线程是 CPU调度的基本单位。CPU 有时间片的概念, 会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行, 在时间片耗尽之后, 就会失去 CPU 使用权。所以在多线程场景下, 由于时间片在线程间轮换, 就会发生原子性问题。
在上一篇文章中, 我们介绍 synchronized 的时候, 提到过, 为了保证原子性, 需要通过字节码指令 monitorenter 和 monitorexit, 但是 volatile 和这两个指令之间是没有任何关系的。
所以, volatile 是不能保证原子性的。在以下两个场景中可以使用 volatile 来代替 synchronized:1、 运算结果并不依赖变量的当前值, 或者能够确保只有单一的线程会修改变量的值。2、 变量不需要与其他状态变量共同参与不变约束。
总结与思考
我们介绍过了 volatile 关键字和 synchronized 关键字。现在我们知道,synchronized 可以保证原子性、 有序性和可见性。而 volatile 却只能保证有序性和可见性。