transient
对于transient 修饰的成员变量,在类的实例对象的序列化处理过程中会被忽略。 因此,transient变量不会贯穿对象的序列化和反序列化,生命周期仅存于调用者的内存中而不会写到磁盘里进行持久化。
Java中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息,一个序列化后的对象可以被写到数据库或文件中,也可用于网络传输。
在持久化对象时,对于一些特殊的数据成员(如用户的密码,银行卡号等 涉及安全性的数据),我们不想用序列化机制来保存它。为了在一个特定对象的一个成员变量上关闭序列化,可以在这个成员变量前加上关键字transient。
static修饰的静态变量天然就是不可序列化的。
transient关键字只能修饰变量,而不能修饰方法和类。(本地变量是不能被transient关键字修饰的。)
变量如果是用户自定义类变量,则该类需要实现Serializable接口。
volatile
volatile同样用于修饰类属性,不能用于修饰本地变量
volatile可以理解为一种轻量级的同步机制(区别于锁机制),能够减少线程上下文的切换和调度,但volatile的同步性也没有锁的强,对于一些非原子性的操作还是不能做到完全保证同步。
先来看看volatile的实现原理吧:
Java的内存模型
所有线程的共享变量都存储在主内存中,每一个线程都有一个独有的工作内存,每个线程不直接操作在主内存中的变量,而是将主内存上变量的副本放进自己的工作内存中,只操作工作内存中的数据。当修改完毕后,再把修改后的结果放回到主内存中。每个线程都只操作自己工作内存中的变量,无法直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
如果对变量加上volatile关键字后,如果某线程A修改了该变量后,会立即将变量的值刷到主内存中,其他线程中的变量失效,必须从主内存中重新获取。
底层原理:
volatile可见性是通过汇编加上Lock前缀指令,触发底层的MESI缓存一致性协议来实现的。
状态 | 描述 |
---|---|
M 修改 (Modified) | 此时缓存行中的数据与主内存中的数据不一致,数据值存在于本工作内存中,其他线程从主内存中读取共享变量值的操作会被延迟执行,直到该缓存行数据将数据写回到主内存后 |
E 独享 (Exclusive) | 此时缓存行中的数据会与主内存中的数据一致,数据只存在本工作内存中。此时会监听其他线程读主内存中共享变量的操作,如果发生,该缓存行需要变成共享状态 |
S 共享 (Shared) | 此时缓存行中的数据与主内存中的数据一致,数据存在于很多工作内存中。此时会监听其他线程使该缓存行无效的请求,如果发生,该缓存行需要变成无效状态。 |
I 无效 (Invalid) | 此时该缓存行无效 |
简单翻译一下:获取volatile的所有线程都会监听内存总线中该变量的获取状态。如果一个线程正在对某volatile修饰的变量修改时,其他线程中的值会变为无效状态,等待修改完成,再重新去主内存中获取新的值。
在这种模式下会出现一些并发问题,例如:a++这种自增的操作,但并非原子性的情况
时间 | 操作 |
---|---|
1 | 线程A获取到a |
2 | 线程B获取到a |
3 | 线程A:a++ |
4 | 线程B:由于线程A的修改,导致线程B的值失效,不执行操作 |
因此,想完全实现同步,还需要引入锁机制
参考:CSDN博主「天瑕」的原创文章
而volatile另一个特性则是禁止指令重排序,底层原理则是在指令间通过内存屏障实现。
instanceof
判断一个对象是否是一个类的实例,返回值为true/false
ObjectName instanceof ClassName