Java 的 idiom(8)

多线程

记住 mutable 数据的并行访问必须是 synchronized。即便是 boolean 的访问本身是 atomic 的,但是由于某些优化可能导致一些 pattern 失效,常见的是为了避免调用 Thread.stop,往往提供一个 boolean 供线程判断是否结束,一般会写个 while (flag) 的循环,但这一般会被自动优化成为 if (flag) while(true),为了避免这个问题或者提供 synchronized 的 getter/setter 或者声明此变量为 volatile 让编译器放弃这种优化。

进行多线程化的时候,最好是把 mutable 数据分别放到每个线程里面去(如果可能的话),这样就避免了多线程的访问。

同时也需要避免过度的使用 synchronized,这会带来额外的代价,前面提到了 singleton 的 getInstance 如果直接使用 synchronized 就会造成额外的 overhead。在写 synchronized block 时注意不要调用别的非 synchronized 对象的方法,因为这些方法可能又会调用你的对象别的 synchronized 方法:如果是同一个线程,Java 就知道这是一个 ConcurrentModificaitonException,如果是从另外一个线程来做这个事情则会死锁(因为你的对象在 lock 状态,别的线程调用 synchronized 方法的结果就是被挂起等待,这样等待子线程结束是不可能完成的)。

尽量利用 executor 而减少 Thread 的使用。executor 和 task 概念上简单,将任务调度和任务解耦合,避免了直接使用 Thread 产生的问题。尽量使用一些现成的并行工具(blocking queue),避免直接使用语义较低的 wait/notify 之类的。使用 System.nanoTime 而避免 currentTimeMillis(更精确,不受系统时钟调整的影响)。

为提供的对象提供 thread safety 的注释,帮助 client 理解如何在多线程环境下使用。常见分类有 immutable(multi-thread friendly)、unconditionally thread-safe(内部已經考虑到多线程的问题,通过 synchronization 避免了可能的问题)、conditionally thread-safe(某些方法可能需要外部的 synchronization)、not thread-safe(mutable,未作保护)、 thread-hostile(即使添加外部的 synchronization 也无法避免问题)。

谨慎使用 lazy initialization,这个对 singleton 的例子就是印证。一种更有用的策略是使用 holder class,这是通过一个 inner static class 存放这个对象,然后通过 static 函数返回这个类里面的静态成员,这能 work 的原因是这个 inner class 只在第一次调用获得实例的方法时被载入内存。如果实际可以多次初始化对象也可以用 single check(不需要 synchronized 同时 volatile 可甚至可以去掉)。

不应该过分依赖 scheduler 的准确性,而应该从设计上保证可行性。避免使用过气了的 ThreadGroup。

串行化

谨慎实现 Serializable,注意的是实现这个接口后就很难修改实现(因为你得处理不同实现下 serialization 的结果否则就不兼容了)。同时这会产生额外的 bug 和安全问题,增加新版本测试负担。需要被继承的类很少需要串行化。需要串行化的类一般需要提供 default constructor,inner class 不应该实现 Serializable 接口。

默认的串行化如果是可以接受的,那么你也应该提供 readObject 用来去串行化,这是因为为了实现和 constructor 类似的检查以免产生不合法的对象。

为需要串行化的类提供 UID 这样可以检查版本兼容性。同时 readObject 应该做 defensive copy 避免被黑。如果希望控制实例个数,优先选择 enum。

一种常见的避免串行化产生的各种问题的方式是使用一个 proxy,这几乎就是待串行化类的完整 copy,因此通常放在 inner static class 里面,其作用就是如果串行化,首先创建一个等价的 proxy 实例;如果去串行化则通过它的 readResolve 来调用待串行化类的构造函数,这样就能保证产生的对象符合要求了。这样一来原类的 readObject 应该抛出异常(禁止误用),并提供 writeReplace 方法以供调用 proxy 的构造函数。

——————
And Esau was forty years old when he took to wife Judith the daughter of Beeri the Hittite, and Bashemath the daughter of Elon the Hittite:

Advertisements
Java 的 idiom(8)

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s