使用Immutable对象解决线程安全
何为Immutable对象?简单地说,如果一个对象实例不能被更改就是一个Immutable的对象,Java SDK提供的大量值对象,比如String等都是Immutable的对象。
如何使对象Immutable?
按照Effective Java的说明,需要满足下面几条规则:
保证类不能被继承- 为了避免其继承的类进行mutable的操作
移调所有setter/update等修改对象实例的操作
保证所有的field是private和final的
为什么要采用Immutable对象?
在并发程序中,使用Immutable可以既保证线程安全性,跟并发锁方式相比,它大大增强了并发时的效率。尤其当一个对象是值对象时,更应该考虑采用Immutable方式。
为了说明,这里先举一个Mutable的非线程安全的例子。person应该是一个典型的值对象,但下面的例子没有使他具备Immutable特性。
//non thread-safe
public class ImmutableDemo {
static MutablePerson testM = new MutablePerson("joanieM", 14);
public static void main(String[] args) {
Thread t1 = new MutableTestThread(1);
t1.start();
Thread t2 = new MutableTestThread(2);
t2.start();
}
}
class MutablePerson {
private int age; //Rule 1: all fields are private and final
private String name;
public MutablePerson(String name, int age) { //rule 2: a factory method pattern is adopted to create the object
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String toString() {
return name +": "+age +"year(s) old";
}
public void updatePerson(String name, int age){
this.name = name;
this.age = age;
System.out.println(this);
}
}
class MutableTestThread extends Thread {
final int MAX=10;
final int idx;
public MutableTestThread(int idx) {
this.idx = idx;
}
public void run() {
for (int i = 0; i < MAX; i++) {
ImmutableDemo.testM.updatePerson("joanieM"+idx, idx*MAX+i);
try {
Thread.sleep(20+i*2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
运行结果:
joanieM1: 10year(s) old
joanieM2: 20year(s) old
joanieM2: 11year(s) old
joanieM2: 11year(s) old
joanieM2: 22year(s) old
joanieM2: 22year(s) old
joanieM2: 13year(s) old
joanieM2: 13year(s) old
joanieM2: 14year(s) old
joanieM2: 14year(s) old
joanieM1: 15year(s) old
joanieM2: 25year(s) old
joanieM1: 16year(s) old
joanieM1: 16year(s) old
joanieM2: 27year(s) old
joanieM1: 27year(s) old
joanieM1: 18year(s) old
joanieM2: 28year(s) old
joanieM2: 29year(s) old
joanieM1: 19year(s) old
结果中红颜色标注的都是错误的结果。由于没有采取任何保证线程安全性的操作,首先线程t1执行完updatePerson函数的this.age=age后被挂起,线程t2执行完updatePerson函数的this.name=name后被挂起,线程t1继续执行,此时的name值为t2执行的结果joanieM2,age则为t1执行的结果,于是打印出了错误的值:joanieM2: 11year(s) old
下面的例子给出了如何使用Immutable来保证值对象的线程安全性的。
public class ImmutableDemo {
//test is a shared thread-safe object.
static ImmutablePerson test = ImmutablePerson.getPerson("joanie", 14);
public static void main(String[] args) {
Thread t1 = new TestThread(1);
t1.start();
Thread t2 = new TestThread(2);
t2.start();
}
}
//a sample immutable class
//Rule 4: define class as final one
final class ImmutablePerson {
private final int age; //Rule 1: all fields are private and final
private final String name;
private ImmutablePerson(String name, int age) { //rule 2: a factory method pattern is adopted to create the object
this.age = age;
this.name = name;
System.out.println(this);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String toString() {
return name +": "+age +"year(s) old";
}
//Rule 3: no setters for value update. Create a new class instead
public static ImmutablePerson getPerson(String name, int age) {
return new ImmutablePerson(name, age);
}
}
class TestThread extends Thread {
final int MAX=10;
 
补充:综合编程 , 安全编程 ,