
Javalang.string类用于表示字符串。String类中并没有修改字符串内容的方法。也就是说,String的实例所表示的字符串的内容绝对不会发生变化。
正因为如此,String类中的方法无需声明为synchronized。因为实例的内部状态不会发生改变,所以无论String实例被多少个线程访问,也无需执行线程的互斥处理。
我们下面将学习Immutable模式。Immutable就是不变的,不发生改变的意思。Immutable模式中存在着确保实例状态不发生改变的类(Immutable类)。在访问这些实例时并不需要执行耗时的互斥处理,因此若能巧妙利用该模式,定能提高程序性能。
Immutable的反义词是Mutable(易变的)。在设计类或理解已有类的时候,大家一定要注意“这个类是不变的还是易变的”,即注意类的不可变性(immutability)。String就是一个Immutable类。
下面我们编写一个使用Immutable模式的简单程序。
Person : 表示人的类
Main : 测试程序行为的类
PrintPersonThread 显示Person实例的线程的类
使用Immutable模式的Person类
Person类用于表示人,包含姓名(name)和地址(address)这两个字段。
Person类的字段值仅可通过构造函数来设置。类中设有引用字段值的getName方法和getAddress方法,当并没有修改字段值的set方法.
因此,Person类的实例一旦创建,其字段的值就不会发生改变。
这时,即便多个线程同时访问同一个实例,Person类也是安全的。Person类中的所有方法也都允许多个线程同时执行。Person类中的getName,getAddress和toString这三个方法都无需声明为synchronized。
Person类声明为final类型,这就表示我们无法创建Person类的子类。虽然这并不是Immutable模式的必要条件,但确实防止子类修改其字段值的一种措施。
Person类的字段name和address的可见性都为private。也就是说,这两个字段都自由从该类的内部才可以访问。这也不是Immutable模式的必要条件,而是防止子类修改其字段值的一种措施。另外,Person类的字段name和address都声明为final,意即一旦字段被赋值一次,就不会再被赋值。这也不是Immutable模式的必要条件,只是为了明确编程人员的意图。这样一来,即使不小心写了赋值代码,在编译时也会有错误提示。
public classPerson {
private finalStringname;
private finalStringaddress;
publicPerson(String name, String address) {
this.name= name;
this.address= address;
}
publicString getName() {
returnname;
}
publicString getAddress() {
returnaddress;
}
publicString toString() {
return"Person{"+
"name='"+name+'\''+
", address='"+address+'\''+
'}';
}
}
Main类会创建一个Person的实例(alice),并启动三个线程访问该实例。
Public class Main{
public static voidmain(String[] args) {
Person alice =newPerson("Alice","Alaska");
newPrintPersonThread(alice).start();
newPrintPersonThread(alice).start();
newPrintPersonThread(alice).start();
}
}
PrintPersonThread类用于持续显示构造函数传入的Person类的实例。显示的字符串格式如下。
Thread.currentThread().getName()+”prints”+Person
其中,下面这条语句用于获取自身线程的名称。
Thread.currentThread().getName()
此处Thread.currentThread方法用于获取当前的线程,即获取调用currentThread方法的线程本身。currentThread是Thread类的静态方法,而getName是Thread类的实例方法,用于获取线程的名称。
另外,如下语句(1)的含义等同于(2)
...+”prints”+person (1)
...+”prints”+person.toString() (2)
字符串的实例表达式通过运算符“+”连接时,程序会自动调用实例表达式的toString方法(这是Java规范)
Public class PrintPersonThread extends Thread{
Private Person person;
Public PrintPersonThread(Person person){
This.person = person;
}
Public void run(){
While(true){
System.out.println(Thread.currentThread().getName()+”prints”+Person);
}
}
}
运行结果实例如图所示,从图中可以看出,确实有多个线程在调用toString方法,但是(也是理所当然地)值未出现异常。因为想要破坏它也破坏不了。
实例程序的类图:
在上图中,字段名后面都添加了{frozen}约束(frozen也就是“冻结”的意思)。这是UML的标识法,表示“实例被创建且字段被初始化之后,字段的值就不会再被修改”。这对应于java中的“final字段”。
另外,方法名后面都添加了{concurrent}约束(concurrent也就是“并发”的意思)。这也是UML的标识法,它明确表示“多个线程同时执行也没关系”。这对应于Java中的“无需声明为synchronized”方法(即使不加synchronized也能正确执行)。
现在我们回想下Single Threaded Execution模式,该模式会将修改或引用实例状态的地方设置为临界区,使这个区域只能用一个线程同时执行.但像Person类这样,实例的状态绝对不会发生改变时,情况就不一样了。即使多个线程同时对实例执行处理,实例也不会出错,因为实例的状态肯定不会发生改变。既然实例的状态肯定不会发生改变,那么也就无须使用synchronized来保护实例。因为即使想破坏实例,也破坏不了。
对于适用Immutable模式的类(Immutable类),我们无需再使用synchronized方法执行线程的互斥处理,因为即使不使用synchronized,也能确保安全性。虽然到此为止的内容全都是该模式的优点,但确保Immutability是一项出乎意料的难题,还请大家注意。