线程安全级别
安全程度从强到弱:不可变 绝对线程安全 相对线程安全 线程兼容 线程对立
- 不可变 不可变的对象一定是线程安全的(前提this引用没有逃逸)
- 基本类型只要定义未final,就可以保证不可变
- 如果是引用类型,需要保证对象的状态不会受对象行为影响;所以引用类型的不安全在于可能线程一判断某对象状态后,另一线程进行了改变。
- 符合不可变要求的类型:String、Number的子类:Integer、Long、Short、、、(除BigInteger、BigDecimal等由数组存储的值和AtomicInteger、AtomicLong类型)
- 绝对线程安全: 任何情况下,调用者不需要任何同步措施,仍可以安全运行。很难实现,jdk提供的类也大多不是绝对线程安全。(不可变一定是绝对线程安全)
- 相对线程安全:通常意义上的线程安全——对某个对象的单独的操作是线程安全的。
- 线程兼容:大多数jdk的api都是这样——调用端正确使用同步可以保证线程安全
- 线程对立:不管采用什么同步措施,都会线程不安全;例如:
- Thread.suspend和Thread.resume 两线程同时对另一线程分别调用这两个方法必定产生死锁
- 原因:suspend停止不释放资源,当另一线程resume之前锁定已被锁定资源,必定产生死锁
线程安全实现方法
- 互斥同步/阻塞同步:互斥是方法,同步是目的;最大的问题:线程阻塞和唤醒带来的性能问题
- 互斥实现方法:
- 临界区(CriticalSection)
- 信号量(Semphore)
- 互斥量(Mutex)
- java中的实现方法:
- synchronized:
- 同步代码块编译之后,代码块前后会出现moniterenter和moniterexit的字节码指令,这两个命令需要引用类型的参数来指明需要锁定和解锁的对象
- (为什么不能锁基本类型嘞??)加锁是为了保证只有一个线程可以访问该变量,进而实现同步,但是基本类型可以直接final不需要多此一举。编译器报错——需要Object类型而不是int类型
- 如果synchronized修饰方法:实例方法锁定实例、类方法锁定类对象
- 执行moniterenter时首先尝试获取锁,如果当前无锁或者锁的拥有者是当前线程,锁的计数器加一,否则当前线程等待直到对象锁被释放
- synchronized重量锁:阻塞或唤醒线程需要从用户态到内核态
- synchronized 可重入
- 优化 : 自旋锁,循环判断是否锁被释放,假如锁被释放了,就不需要阻塞线程,不浪费cpu时间
- reentryLock:基本用法和synchronized相同,还增加了些高级功能:
- 等待可中断
- 公平非公平(synchronized的是否公平?非公平)
- 锁可以绑定多个条件 new Condition之后await阻塞,signalAll打断
- synchronized:
- 互斥实现方法:
- 非阻塞同步:乐观的并发策略,先进性操作,如果没有其他线程争用,则操作成功,如果操作失败,需要进行补救(比如不断重试);
- 这种实现需要硬件的支持:因为冲突检测和操作需要原子性,这类指令有:CAS(其余的P394)
- 无同步方案 假如一个方法本来就不涉及共享数据,那它就无须任何同步
- 可重入代码 共同特征:
- 不依赖存储在堆上的数据和公用的系统资源、用到的状态变量都由参数传入、不调用非可重入的方法等
- 判断方法:如果一个方法的返回结果可预测——只要输入了相同的树,就能返回相同的结果,那就满足可重入性,也就线程安全
- 线程本地存储:如果一个代码需要的数据必须与其他代码共享,假如这些共享数据的代码可以在同一线程内运行,那么就无须同步也可以保证线程之间不出现数据争用问题。java中的ThreadLocal
- 可重入代码 共同特征: