- 后果(以单例模式举例)
竞态条件(单例模式懒加载 先检查后实例化,行为不正确不能保证单例)
对象状态不一致,如一个对象一致性状态变量A+B=C,如果对A、B、C的修改不能原子性地完成,出现不一致
丢失更新,共享变量自增count++ ,如100个线程跑完却没有增加100
- fix方案
加锁
辨别一个类是否是线程安全的
只读共享
样例1
1、
a、不可变对象如String
b、对象创建以后状态就不能改变
c、对象的所有字段field都是final
d、对象正确创建(创建期间this没有暴露出去)
2、代码解释
private final char value[];//可变对象基础上构建不可变类 public char[] toCharArray() { char result[] = new char[value.length]; System.arraycopy(value, 0, result, 0, value.length); return result; }// 没有加char[]数组发布出去,不会被意外修改
样例二
事实不可变对象
线程封闭
样例1
1、
栈封闭
无状态没有实例成员变量
栈(局部变量)在运行时是线程私有的
2、代码解释
没有共享变量,不存在以上问题,无需可见、和有序, 局部变量是私有,任何操作不影响其它线程,完全隔离
样例二
ThreadLocal 对象仅由一个线程修改
线程安全共享
1、
在内部实现同步
多个线程通过共有public接口访问无需同步
2、hashtable所有的public访问方法都用synchronized修饰
保护对象
1、有锁才能访问保护对象
2、
hashtable发布内部对象时,用本身的对象锁保护
keySet = Collections.synchronizedSet(new KeySet(), this);
为什么hashmap不安全
public class SafeSet { // final private final Set<Integer> myset = new HashSet<>(); //饿汉 一旦创建不会发布出去 // synchronized 同步访问 public synchronized void addInteger(Integer p){ myset.add(p); } public synchronized boolean containsInteger(Integer p) { return myset.contains(p); } }
- hashMap线程不安全
1.public方法没有做任何同步操作,会引发以上3种问题(原子、可见、有序)
2.不安全地发布共享实例变量
keySet等方法直接返回对象的内部变量,破坏了封装,有可能在外部不通过其接口方法修改了其状态
- hashtable线程安全
1.所有的public访问方法都用synchronized修饰,互斥访问,线程安全共享,与上文中SafeSet实现思路相同
2.安全发布共享实例变量
values、keySet等方法返回的keySet = Collections.synchronizedSet(new KeySet(), this);还是受锁保护 保护对象