线程安全的途径:
安全发布对象-发布与逃逸
- 发布对象:使一个对象能够被当前范围之外的代码所使用
- 对象逃逸:一种错误的发布。当一个对象还没有构造完成的,就使它被其他线程所见
安全发布对象(单例模式:列一下)
单例模式中的枚举方式是最安全的,双重锁的时候加volitale关键字才是线程安全的。
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保存到volatile类型或者AtomicReference对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 将对象的引用保存到锁保护的域中
躲避并发(1.不可变对象2.线程封闭)
不可变对象
- 对象创建之后其状态就不能修改
- 对象所有域都是final类型
- 对象是正确创建的(在创建期间,this引用没有逃逸)
final关键字:类、方法、变量
修饰类:不能被继承
修饰方法:1、锁定方法不能被继承类修改;2、效率
修饰变量:基本数据类型变量、引用类型变量
其他不可变对象:(多线程情况下不会有线程安全的问题了)
Collections.unmodeifiableXXX:Collection,List,Set,Map,,,
Guava:ImmutableXXX:Collection、List、Set、Map
线程封闭
- Ad-hoc线程封闭:程序控制实现,最糟糕,忽略
- 堆栈封闭:局部变量,并发无问题(不会被多个线程共享,全局的变量容易引起并发的问题)
- ThreadLocal线程封闭:特别好的封闭方法(内部维护了map)
threadlocal(看源码):
线程局部变量,ThreadLocal实例通常来说都是private static类型的。ThreadLocal可以给一个初始值,而每个线程都会获得这个初始值的一个副本,这样就能保证不同的线程都有一个拷贝(每个拷贝之间相互没有影响)
解决:
1.多线程中相同变量的访问冲突问题。
2.数据库连接,先判断connThreadLocal.get()是否为null,如果是null,则说明当前线程还灭有对应的Connection对象,这时创建一个Coonection对象并添加到本地线程变量中;如果不为null,则说明当前线程已经拥有了Connection对象,直接使用就可以了。
//其中public Object get()该方法返回当前线程所对应的线程局部变量。
preHandler
afterHandler
线程不安全的类的写法:
Stringbuilder类:不安全
StringBuffer类:近乎所有方法都加了sychronized的方法
SimpleDateFormat类:不安全
DateTime类(joda-time):安全
同步容器:
ArrayList(不同步)-》Vector,Stack(同步)
HashMap-》HashTable(key、value不能为null)(同步)
Collections.synchronizedXXX(List、Set、Map)(同步)
eg.对集合中的元素进行删除,不建议在foreach循环和利用iterator的过程中进行删除,建议先进行标记,标记之后再进行删除。
并发容器JUC:
ArrayList->CopyOnWriteArrayList(写的使用开辟内存(复制),且用了lock锁)
- 缺点:1.内存2.不能实时读,只能最终一致性(适合读多写少)
- 性质:读写分离,最终一致性,写的时候开辟空间
HashSet、TreeSet->CopyOnWriteArraySet
ConcurrentSkipListSet
HashMap、TreeMap->ConcurrentHashMap(效率是ConcurrentSkipListMap的4倍)
ConcurrentSkipListMap(key有序,支持比ConcurrentHashMap并发性更高)