多线程-不可变模式
始状态,要么抛弃原来的字符串返回一个新字符串,而绝对不会返回被修改了的字符串对象.
但是很多时候返回新字符串抛弃原来的字符串对象这样的操作太浪费资源了.特别是在循环地操作的时候:
String s = "Axman";
for(int i=0;i<1000*1000;i++) s += "x";这样的操作是致命的.
那么这种时候需要将原始的不变的s包装成可变的StringBuffer来操作,性能的改变可能是成千上万倍:
StringBuffer sb = new StringBuffer(s); //将不变的String包装成可变的String;
for(int i=0;i<1000*1000;i++)
sb.append("x");
s = new String(sb); //将可变类封装成不变类.虽然可以调用toString(),但那不是可变到不变的转换.
final class MutableDog{
private String name;
private int age;
public MutableDog(String name,int age){
this.name = name;
this.age = age;
}
public synchronized void setDog(String name,int age){
this.name = name;
this.age = age;
}
public String getName(){return this.name;}
public int getAge(){return this.age;}
public synchronized String toString(){
return "Dog's name = " + this.name + ",age = " + this.age;
}
public synchronized ImmatableDog getImmatableDog(){
return new ImmatableDog(this);
}
}
final class ImmatableDog{
private final String name;
private final int age;
public ImmatableDog(String name,int age){
this.name = name;
this.age = age;
}
public ImmatableDog(MutableDog dog){
this.name = dog.getName();
this.age = dog.getAge();
}
public String getName(){return this.name;}
public int getAge(){return this.age;}
public String toString(){
return "Dog's name = " + this.name + ",age = " + this.age;
}
}
MutableDog类是可变的,可以满足我们利用对象的缓冲来让对象成为表示另一个实体的功能.但它们之间
随时可以根据需要相互转换,但是我们发现:
public ImmatableDog(MutableDog dog){
this.name = dog.getName();
this.age = dog.getAge();
}
这个方法是不安全的.当一个属性为"Sager",100的dog被传进来后,执行this.name = dog.getName();后,
如果线程切换到其它线程执行,那么dog的属性就可能是"p4",80,这时再执行this.age = dog.getAge();
我们就会得到一个属性为"Sager",80的这样一个错误的不可变对象.这是一个非常危险的陷井.在这里我们
可以通过同上来解决:
public ImmatableDog(MutableDog dog){
synchronized(dog){
this.name = dog.getName();
this.age = dog.getAge();
}
}
注意这里同步的MutableDog,它将会和MutableDog的setDog产生互斥.它们都需要获取同一MutableDog对象的
锁,如果MutableDog的setDog不是方法同步(synchronized(this)),即使ImmatableDog(MutableDog dog)中同步
了dog,也不能保证安全,它们需要在同一对象上互斥.
但同步也并不一定能保证传入的参数不可变:
我曾以下面这个例子来作为对一个Java程序员的终极测试,终极测试的意思是说,如果你不懂并不说明你水平
差,但如何你懂这个问题那就没有必要测试其它问题了.
public static void test(Object[] objs){
java.security.BasicPermission bp = xxxxx;
for(Object o: objs){
bp.checkGuard(o);
}
for(Object o: abjs){
o.xxx;
}
}
当一个数据被传入后我们需要对其中的每个元素做安全性检查,如果通不过bp.checkGuard(o);自己会抛出
异常的.但如果objs[0]被bp.checkGuard(o);过后,外面的线程通过objs去修改objs[0],这时就会把一个没有
经过安全检查的对象绕过bp.checkGuard(o);而直接被调用.假如Runtime.exec(String[] args)就是这样实
现我们可以想象会出现什么问题.
所以对于这样的传入参数,我们可以将其在方法类复制为本地变量(数组).或使用它的深度clone,打断与方法
外的联系:
public static void test(Object[] objs){
Object tmp = new Object[objs.lenth];
System.arrayCopy(objs,tmp,0,0,objs.lenth);
java.security.BasicPermission bp = xxxxx;
for(Object o: tmp){
bp.checkGuard(o);
}
for(Object o: tmp){
o.xxx;
}
}
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步