Volatile
Volatile修饰的成员变量在每次被线程访问时,都强迫从主内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到主内存。
这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。
这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。 而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。
使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。 由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
Transient
Transient修饰的成员变量不会被序列化
Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。 为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。 注意static变量也是可以串行化的
example
首先,让我们看一些Java serialization的代码:
class="hibot" style="padding-left: 38px; margin: auto; font-size: 14px; font-family: monospace; background: #111111; color: #e6e1dc;">
-
public?class?LoggingInfo?implements?java.io.Serializable?
- {?
- ????private?Date?loggingDate?=?new?Date();?
- ????private?String?uid;?
- ????private?transient?String?pwd;?
- ?
- ????LoggingInfo(String?user,?String?password)?
- ????{?
- ????????uid?=?user;?
- ????????pwd?=?password;?
- ????}?
- ????public?String?toString()?
- ????{?
- ????????String?password=null;?
- ????????if(pwd?==?null)?
- ????????{?
- ????????????password?=?"NOT?SET";?
- ????????}?
- ????????else?
- ????????{?
- ????????????password?=?pwd;?
- ????????}?
- ????????return?"logon?info:?\\n???"?+?"user:?"?+?uid?+?
- ????????????????"\\n???logging?date?:?"?+?loggingDate.toString()?+?
- ????????????????"\\n???password:?"?+?password;?
- ????}?
- }
现在我们创建一个这个类的实例,并且串行化(serialize)它 ,然后将这个串行化对象写如磁盘。
-
class?Scratch?{?
- ????public?static?void?main(String[]?args)?{?
- ????????LoggingInfo?logInfo?=?new?LoggingInfo("MIKE",?"MECHANICS");?
- ????????System.out.println(logInfo.toString());?
- ????????try?{?
- ????????????ObjectOutputStream?o?=?new?ObjectOutputStream(?
- ????????????????????new?FileOutputStream("logInfo.out"));?
- ????????????o.writeObject(logInfo);?
- ????????????o.close();?
- ????????}?catch?(Exception?e)?{?
- ?????????????
- ????????}?
- ?????????
- ?
- ????????try?{?
- ????????????ObjectInputStream?in?=?new?ObjectInputStream(?
- ????????????????????new?FileInputStream("logInfo.out"));?
- ????????????LoggingInfo?logInfo?=?(LoggingInfo)?in.readObject();?
- ????????????System.out.println(logInfo.toString());?
- ????????}?catch?(Exception?e)?{?
- ?????????????
- ????????}?
- ????}?
- }
如果我们运行这段代码,我们会注意到从磁盘中读回(read——back (de-serializing))的对象打印password为"NOT SET"。这是当我们定义pwd域为transient时,所期望的正确结果。
现在,让我们来看一下粗心对待transient域可能引起的潜在问题。假设我们修改了类定义,提供给transient域一个默认值,代码如下:
-
public?class?GuestLoggingInfo?implements?java.io.Serializable?
- {?
- ????private?Date?loggingDate?=?new?Date();?
- ????private?String?uid;?
- ????private?transient?String?pwd;?
- ?
- ????GuestLoggingInfo()?
- ????{?
- ????????uid?=?"guest";?
- ????????pwd?=?"guest";?
- ????}?
- ????public?String?toString()?
- ????{?
- ?????????
- ????}?
- }
现在,如果我们穿行化GuestLoggingInfo的一个实例,将它写入磁盘,并且再将它从磁盘中读出,我们仍然看到读回的对象打印password 为 "NOT SET"。 当从磁盘中读出某个类的实例时,实际上并不会执行这个类的构造函数, 而是载入了一个该类对象的持久化状态,并将这个状态赋值给该类的另一个对象。