Java发布对象与线程安全思考

发布对象

  • 发布对象:使一个对象能够被当前范围之外的代码所使用

  • 对象逸出:一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见

安全发布对象四种方法

1 在静态初始化函数中初始化一个对象引用

2 将对象的引用保存到volatile类型域或者AtomicReference对象中

3 将对象的引用保存到某个正确构造对象的final类型域中

4 将对象的引用保存到一个由锁保护的域中

私有构造函数,单例对象,静态工厂方法获取对象

以单例模式为例

懒汉模式:单例实例在第一次使用时进行创建(线程不安全)

懒汉模式也可以实现线程安全,给getInstance方法添加synchronized关键字(不推荐,因为性能不好)

双重同步锁单例模式:双重监测机制,在方法内部加synchronized关键字(不是线程安全的)

原因是,创建对象是,分为以下三个步骤:

1) memory = allocate() 分配对象的内存空间

2)ctorInstance() 初始化对象

3)instance = memory() 设置instance指向刚分配的内存

由于JVM和cpu优化,可能会发生指令重排:

1) memory = allocate() 分配对象的内存空间

3) instance = memory() 设置instance指向刚分配的内存

2) ctorInstance() 初始化对象

当以上面这种指令执行时,线程A执行到3 instance = memory() 设置instance指向刚分配的内存 这一步时,线程B执行if(instance == null)这段代码,此时instance != null,线程B直接return instance,导致对象没有初始化完毕就返回

解决办法就是限制对象创建时进行指令重排,volatile+双重监测机制->禁止指令重排引起非线程安全

饿汉模式:单例实例在类装载时进行创建(线程安全)

枚举模式:线程安全

线程不安全类与写法

字符串

StringBuilder:线程不安全

StringBuffer:线程安全

时间转换

SimpleDateFormat:线程不安全

JodaTime:线程安全

集合

ArrayList:线程不安全

HashSet:线程不安全

HashMap:线程不安全

同步容器(在多线程环境下不推荐使用)

ArrayList -> Vector, Stack

Vector中的方法使用synchronized修饰过

Stack继承Vector

HashMap -> HashTable(key、value不能为null)

HashTable使用synchronized修饰方法

Collections.synchronizedXXX(List、Set、Map)

同步容器不完全是线程安全的

编程注意:如果使用foreach或者iterator遍历集合时,尽量不要对集合进行修改操作

并发容器J.U.C(java.util.concurrent)(在多线程环境下推荐使用)

ArrayList -> CopyOnWriteArrayList:相比ArrayList,CopyOnWriteArrayList是线程安全的,写操作时复制,即当有新元素添加到CopyOnWriteArrayList时,先从原有的数组里拷贝一份出来,然后在新的数组上写操作,写完之后再将原来的数组指向新的数组,CopyOnWriteArrayList整个操作都是在锁(ReentrantLock锁)的保护下进行的,这么做主要是避免在多线程并发做add操作时复制出多个副本出来,把数据搞乱了。第一个缺点是做写操作时,需要拷贝数组,就会消耗内存,如果元素内容比较多会导致youngGC或者是fullGc;第二个缺点是不能用于实时读的场景,比如拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到的数据可能还是旧的,虽然CopyOnWriteArrayList能够做到最终的一致性,但是没法满足实时性要求,因此CopyOnWriteArrayList更适合读多写少的场景

CopyOnWriteArrayList设计思想:1读写分离 2最终一致性 3使用时另外开辟空间解决并发冲突

HashSet -> CopyOnWriteArraySet

TreeSet -> ConcurrentSkipListSet

CopyOnWriteArraySet:底层实现是CopyOnWriteArrayList

ConcurrentSkipListSet:和TreeSet 一样支持自然排序,基于map集合,但是批量操作不是线程安全的

HashMap -> ConcurrentHashMap :不允许空值,针对读操作做了大量的优化,具有特别高的并发性

TreeMap -> ConcurrentSkipListMap :内部使用SkipList跳表结构实现的,key是有序的,支持更高的并发

安全共享对象策略——总结

1 线程限制:一个呗线程限制的对象,由线程独占,并且只能被占有它的线程修改

2 共享只读:一个共享只读的对象,在没有额外的同步情况下,可以被多个线程并发访问,但是任何线程都不能修改它

3 线程安全对象:一个线程安全的对象或容器,在内部通过同步机制来保证线程安全,所以其他线程无序额外的同步就可以通过公共接口随意访问它

4 被守护对象:被守护对象只能通过获取特定的锁来访问

不可变对象、线程封闭、同步容器、并发容器

你可能感兴趣的:(Java发布对象与线程安全思考)