java并发编程和高并发之线程安全策略
一、不可变对象:
1、有一种对象,发布时就是安全的,这个对象就是不可变对象。
2、如果想创建一个不可变的类,可参考使用最多的String类。当满足以下三个条件时才会是不可变对象。
即不可变对象需要满足的条件:
》对象创建以后其状态就不能修改;——》声明为final
》对象所有域都是final类型;
》对象是正确创建的(在对象创建期间,this引用没有逸出)——》对 变量不提供set方法,将所有可变的成员声明为final,通过构造器初始化所有成员进行深度拷贝。在get方法中不直接返回对象本身而是返回克隆对象,并返回对象的拷贝。
3、基础:
》final关键字:类、方法、变量
》修饰类:不能被继承。如果希望一个类永远不能被继承,就用final进行修饰。java基本类型的封装类都是用final修饰的不可继承的类。final 类中的成员变量可以根据需要声明为final,但final类中的所有成员方法都会被隐式地指定为final 方法。注意:在用final修饰时,要谨慎地考虑。
》修饰方法:1、锁定方法不被继承类修改;2、效率(旧版本);
扩展:任何一个类的private方法会被隐式地指定为final方法。
》修饰变量:基本数据类型变量、引用类型变量。如果final修饰基本类型变量,那么该变量一旦被初始化,其值将不可被修改。而如果final修饰一个引用类型的变量,则该变量只能指向初始化时的对象,不能再被更改指向另外的对象。但是特别注意刚才所述的最后一条:不可以改变引用变量指向的对象,是指引用变量初始化后,其值表示的是某个对象的地址。因为final修饰的变量一旦初始化,则它的值不可改变,即引用变量的值一直是初始化时某个对象的地址值。故不可改变它(使它指向其他的对象),但可以通过一些方法,让引用变量指向的对象本身发生改变。好比一根绳子牵着一个盒子,这根绳子生生世世只能拉着这个盒子,而无法修改拉住其他盒子。但是盒子本身可以更改其内容物,比如在盒子中增减存放不同的东西。即引用变量表示的对象除了地址值无法发生改变,其其他属性可以改变。
4、代码演示,final修饰基本变量和引用变量的区别;
如果基本类型初始化之后被修改,编译时会直接爆红。
如果是引用类型,比如下方举例的map, map只能指向第一次初始化时创建的这个数组,
但是map本身的key value值是可以被修改的。如下方示例。这点特别注意!!!!!
因此,final修饰引用变量类型,是存在一定的线程安全问题的。(所以克隆时,有深克隆,要求把涉及到的所有引用类型要克隆一遍再使用)
注:上方创建map时使用了guava包,下方可看下其导入的包
运行时会抛出异常
5、java中除了上述final关键字,可以声明不可变对象,还有其他方法吗?
答案:有的,具体如下:
》包括 jdk中的Collections.unmodifiableXXX:Collection 、List、Set、Map ...
使用方法:把自己创建的Collection等对象,放到如上对应的unmodifiableXXX中,该对象即不可变。
》包括谷歌的Guava 中:ImmutableXXX:Collection 、List、Set、Map ...,
使用方法:声明一个Immutable开头的对象时,只要完成其初始化方法,就已经是个不可变对象了。
但是与final修饰变量不可变的表现不同,该种方式是在运行时抛出异常出来。
运行时可以发现:
即发现unmodifiableMap 类中有很多类似的unmodifiXXX的方法,如下图: