共享模型与管程
![](http://39.101.72.240:8080/picture/白妹.jpg)
共享问题
- i++ 不是一个原子的操作
需要先从主内存读取值,然后在线程中+1,然后再把值写回主内存
临界区
多个线程对共享资源读写操作法发生指令交错,就会出现问题
线程安全的问题一般都是由存在临界区的代码导致的
一个代码块内如果对共享变量操作的代码块叫做临界区
竞态条件
在临界区存在并发操作一个资源
synchronized
应用互斥, 防止临界区的竞态条件的发生, 可以有多个手段达到目的
- 阻塞方式
- synchronized
- Lock
- 非阻塞
- 原子变量
如何使用?
直接将一个共享对象作为一个锁对象,注意如果一个线程拿到了锁之后,时间片用完了,并不会释放锁, 其他线程仍然得等这个线程重新获得时间片,然后释放锁之后,才能重新获取锁
用对象锁保证了临界区代码的原子性, 不会被其他的打断, 不会被打断
必须要多个线程的临界区都需要使用同一个对象加锁, 否则等于没加
1 | synchronized (synchronized_demo.class) { |
面向对象的改进
1 | public class synchronized_demo { |
方法上的synchronized
等价与锁自己所在的类的实例
锁静态方法和锁动态方法
1 | class Test{ |
等价于
1 | class Test{ |
锁静态方法等价于锁类的class对象
线程安全分析
- 没有共享就线程安全
- 如果被共享了
- 如果是只读操作,也安全
- 如果有读写操作, 临界区需要考虑线程安全问题
栈线程私有, 堆线程共享
如果线程的局部变量的引用暴露给了其他的线程, 其他的线程可以通过引用访问这个变量, 也会造成线程不安全
可以使用访问修饰符, 以及final 不可集成
开闭原则, 向子类关闭, 防止子类继承之后, 重写方法, 新建线程,暴露引用导致线程不安全
这里的线程安全是指: 多个线程调用他们的同一个实例的某一个方法的时候, 也是线程安全的
spring中的对象都是单例的, 直接使用线程不安全 需要使用方法中的局部变量
防止外星方法 使用final
Monitor(管程)
java对象头
普通对象头:
mark word / klass word
mark word 结构
普通状态
hashcode 25 age 4 biased_lock 0(偏向锁) | 01 (加锁状态)
数组对象头 加上一个数组长度
monitor详解
当对象被syn锁之后, 就会指向一个monitor的指针
monitor是一个os级的对象, 在jvm中无法表示
加锁过程
monitor由三个部分组成: WaitSet, EntryList, Owner
当一个对象被上锁之后, 首先会把对象的对象头的markword设置成指向monitor的指针, 然后线程指向Owner (通过owner
关联)
另一个线程在获取锁的时候, 首先检查这个对象的markword, 如果这个markword已经指向monitor, 就说明已经上锁, 这个时候将挂载entryList(阻塞队列)后面, 同时进入阻塞
解锁之后, 再把markword 恢复
注意一个对象关联一个monitor, 必须要锁同一个对象才会有效果
轻量级锁
一个对象, 虽然有多线程访问, 但是多线程访问的是错开的, 可以使用轻量级锁 (如果有竞争, 会直接升级成重量级锁)
轻量级锁对于使用者是透明的, 直接使用synchronized即可