1.java内存模型
1.1 原子性
1.2 问题分析
这里与局部变量自增不同,局部变量调用iinc是在局部变量表槽位上进行自增。
静态变量是在操作数栈自增。
这里的主内存和工作内存时再JMM里的说法。
因为操作系统是时间片切换的多个线程轮流使用CPU.
1.3解决方法
JMM中通过synchronized(同步关键字)保证原子性。
使用synchronized减i++和i--的分别的所有字节码指令作为一个整体运行。
使用synchronized加锁的力度最好大一点,只锁个i++就只有四条指令,不然增加时间。
2.可见性
2.1退不出的循环
运行发现停不下来了。
运行超过一秒之后就触发C2编译器进行优化了。run被读到了线程的局部变量表里面。
1s后再修改也看不见了。
2.2解决方法
2.3可见性
volatile只适用于一个写线程和多个读线程的情况。
println底层有synchronized关键字,也可以强制线程去到主存里面取值。
synchronized可以保证可见性和原子性。
3.有序性
3.1诡异的结果
应该是指令顺序为了优化发生了改变导致ready=true时num还没获取到2。
3.2解决方法
@Outcome注解就是检查感兴趣的结果。
1或4就划分为可接受的,ok表示之中
0划分到另一个。
清除并重新编译
生成一个源码jar包和一个压测入口jar包。
运行测试包进行压测。
结果中有两种,一个是带了关闭了分层编译,还有一个是没带任何参数。
两种情况都有出现0的结果。
说明指令重排问题确实有。
解决方法就是使用volatie关键字。
再次压测就不会有指令重排的问题了。
3.3有序性理解
双重检测法创建单例 就需要volatile防止指令重排
如果在创建的代码上加锁力度就太大了,创建该对象了,后续get该对象是不需要加锁的。
所以有了上面的双重判断,先判断是否实例为空,为空就加锁,加完锁再判断实例为空,任然为空就创建。
第一个if是为了提高效率,实例创建后,就不用一直获得锁对象。
第二个if是防止别的线程创建另一个实例。
0分配空间,3复制多一个引用进操作数栈4一个引用去调用构造方法7另一个引用交给了静态变量
t2直接返回了拿到了一个不完整的实例。
3.4 happens-before
就相当于打个标记,标记前改了值, 标记后看得见
4.CAS与原子类
4.1 CAS
CAS 是 Compare And Swap( 比较并替换**)**的缩写,当值为预期值的时候,就将该值替换为预期的值。
CAS 也是实现原子操作的一种方法。
4.2乐观锁与悲观锁
4.3原子操作类
测试结果为0。
5.synchronized 优化(这个部分要先去看JUC)
5.1轻量级锁
A有两次加锁,一次轻量锁,一次重量锁。升级过程就是锁膨胀。
轻量级锁的加锁过程。
线程和对象之间交换定情信物,对象给出了Mark World存在线程的锁记录里面,线程给出了锁记录地址。
Mark Word只有八个字节,解锁时才会将对象的Mark Word恢复。
锁了A之后去访问B尝试锁B结果发现已经锁了,但是是自己上的锁所以还是可以访问B.
然后都访问完了之后就A和B都解锁。
解锁过程是把MarkWorld都还回去然后取出对象上的锁标记。
5.2锁膨胀
升级为重量级锁会把标记从01变成10,并在对象头里面加入重量级锁的指针,该指针用于线程1在解锁时唤醒阻塞中的线程。
5.3重量锁_自旋
这里线程2不会立刻 阻塞,阻塞需要把当前状态保存下来。
直接采用了自旋优化,先不停,不停重试,在阈值之内等到了对象解锁
自旋失败 就进入阻塞状态了。
5.4 偏向锁
锁重入就是要锁不同的代码块时对同一个对象加锁。
5.5其它优化
上锁时间过长可能会导致轻量锁变成重量锁。