【2.1 Immutable模式】
Java.lang.String类用于表示字符串。String类中并没有修改字符串内容的方法也就是说,String的实例所表示的字符串的内容绝对不会发生变化。
因此String类中的方法无需声明为synchronized。因为实例内部状态不会发生改变,所以无论String实例被多个线程访问,也无需执行线程的互斥处理。
Immutable就是不变的、不发生改变的意思。Immutable模式中存在着确保实例状态不发生改变的类。
【2.2示例程序】
public final class Person {
private final String name;
private final String address;
public Person(String name,String address) {
this.name=name;
this.address=address;
}
public String getName() {
return name;
}
public String address() {
return address;
}
public String toString() {
return "[ Person:name = "+name+", address = "+address+" ]";
}
}
Person类声明为了final类型,这就表示我们无法创建Person类的子类。这并不是Immutable模式的必要条件,但却是防止子类修改其字段的一种措施。
Person类的字段name和address的可见性都为private。也就是说,这两个字段都只有该类的内部才可以访问。这也不是Immutable模式的必要条件,而是防止子类修改其字段的一种措施。
另外,Person类的字段name和address都声明为了final,意思是一旦字段被赋值一次,就不会再被赋值。这也不是Immutable模式的必要条件。
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);
}
}
}
Thread.currentThread().getName()用于获取自身线程的名称
字符串和实例表达式通过“+”运算符连接时,程序会自动调用实例表达式的toString方法。
【2.3 Immutable模式中登场的角色】
Immutable角色是一个类,在这个角色中,字段的值不可以修改,也不存在修改字段内容的方法。Immutable角色的实例被创建后,状态将不再发生变化。这时,无需对Immutable角色应用Single Threaded Execution模式,也就是说,无需将Immutable角色的方法声明为synchronized.
【2.4 标准类库中用到的Immutable模式】
String,BigInteger,Pattern,Integer
【2.5 相关的设计模式】
Single Threaded Execution模式
在Immutable模式中,实例的状态不发生变化,所以无需进行保护。
而在Single Threaded Execution模式中,当一个线程正在修改实例状态时,不允许其他的线程来访问该实例。此时会出现如下两种情况之一:
·写入与写入的冲突(write-write conflict)
当一个线程正在修改实例状态(write),而其他线程也在试图修改其状态(write)时发生的冲突。
·读取与写入的冲突(read-write conflict)
当一个线程正在读取实例状态(read),而其他线程也在试图修改其状态(write)时发生的冲突。
Read-Write Lock模式
在Immutable模式中,多个线程之间只会发生read-read的情况。因此,多个线程可以自由地访问实例。
Read-Write Lock模式也利用了read-read不会引起conflict的特点。在Read-Write Lock模式中,执行read的线程和执行write的线程是分开考虑的。发生write-wrte conflict或read-write colflict时,需要执行线程的互斥处理,而发生read-read时就不需要执行线程的互斥处理,这会提高程序性能。
public class Main1 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
new WriterThread(list).start();
new ReaderThread(list).start();
}
}
public class WriterThread extends Thread {
private final List<Integer> list;
public WriterThread(List<Integer> list) {
super("WriterThread");
this.list=list;
}
public void run() {
for(int i=0;true;i++) {
list.add(i);
list.remove(0);
}
}
}
public class ReaderThread extends Thread {
private final List<Integer> list;
public ReaderThread(List<Integer> list) {
super("ReaderThread");
this.list=list;
}
public void run() {
while(true) {
for(int n:list) {
System.out.println(n);
}
}
}
}
ArrayList类在被多个线程同时读取而失去安全性时,便会抛出ConcurrentModificationException异常
java.util.ArrayList是非线程安全的类,但如果使用Collections.synchronizedList方法进行同步,就能得到线程安全的实例。
public class Main1 {
public static void main(String[] args) {
final List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
new WriterThread(list).start();
new ReaderThread(list).start();
}
}
public class ReaderThread extends Thread {
private final List<Integer> list;
public ReaderThread(List<Integer> list) {
super("ReaderThread");
this.list=list;
}
public void run() {
while(true) {
synchronized (list){
for(int n:list) {
System.out.println(n);
}
}
}
}
}
写线程是显式调用add方法和remove方法,无需修改;读线程是隐式调用迭代器,需要修改其代码。
【练习题1 基础知识测试】
阅读下面的内容,叙述正确请打√,错误请打×。
1.java.lang.String类是immutable类。
√
2.java.lang.StringBuffer类是immutable类。
×。StringBuffer类是表示字符串的mutable类。StringBuffer表示的字符串能够随便改写,为了确保安全,改写时需要妥善使用sunchronized.
3.声明为final的字段不可以被赋值两次。
√
4.声明为private的字段可由所在类及子类直接访问。
×。仅可由声明该字段的类本身直接访问。
5.将方法声明为synchronized也不会有什么问题,所以应该尽可能地加上synchronized。
×。有可能会降低类的生存性或性能。
【练习题2 String类真的是immutable类吗】
答:s中保存的实例内容并没有被改写。replace方法会创建一个实例,用来保存替换后的字符串中的字符,并将该实例作为返回值。
public static void main(String[] args) {
String s="BAT";
System.out.println(s.replace('B', 'C'));
}
【练习题3 明明没有settter方法,却不是immutable类】
下面的UserInfo类用于表示用户的信息。在该类中,info字段为private final类型,也没用setter方法,但UserInfo类并不是immutable类,为什么?
public class UserInfo {
private final StringBuffer info;
public UserInfo(String name,String address) {
this.info = new StringBuffer("<info name=\"" +name+"\" address=\""+address+"\"/>");
}
public StringBuffer getInfo(){
return info;
}
public String toString() {
return "[ UserInfo: " + info +" ]";
}
}
答:因为如果修改了getInfo方法的返回值,info字段所引用的实例内容也会发生变化。
public static void main(String[] args) {
//创建实例
UserInfo userinfo = new UserInfo("Alice","Alaska");
//显示
System.out.println("userinfo = "+userinfo);
//修改状态
StringBuffer info = userinfo.getInfo();
info.replace(12, 17, "Bobby");
//再次显示
System.out.println("userinfo = "+userinfo);
}
String类的replace方法并不会修改实例本身,但StringBuffer类的replace方法却会修改实例本身。由于info字段声明为了final,所以info字段的值本身并不会改变。但info字段所指向的实例的状态却有可能改变。