使用多线程去访问共享资源,有可能会带来数据安全性问题。
线程安全性问题体现为 原子性、可见性、有序性。
i++ 非原子性操作,实际上有3个指令,取值,运算,赋值。
解决的方法:锁
synchronized(非公平锁):可以修饰方法,代码块;锁的范围:类锁、对象锁。
锁的存储:在对象头中的mark owrd,最后两位代表锁的状态,前一位为是否偏量锁。无锁(001)、偏向锁(101)、轻量级锁(00)、重量级锁(10)、GC标记(11)。
对象在内存中的布局:对象头、实例数据
锁的状态:偏向锁、轻量级锁、重量级锁。
数据在Java中是采用大端存储的。
使用synchronized加锁会带来性能开销,如何在不加锁的情况下保证线程安全?锁的升级。
偏向锁的抢占:在没有竞争的情况下,线程会去访问对象头中的线程id是否为空或为当前线程的id,满足的情况下,会通过CAS操作将对象头中的线程id修改为当前线程id。即线程获得了偏向锁。这个时候如果有另外的线程在获取偏向锁,如果CAS修改成功,则获取到偏向锁,不成功的话,会去撤销偏向锁,撤销的时候,已经获得偏向锁的线程要么已经执行结束,要么处于安全点。撤销之后,锁升级成轻量级锁。
轻量级锁的抢占:线程抢占锁的时候复制对象头的mark owrd到线程的栈帧中,然后通过CAS将mark owrd中的指针指向栈帧,如果成功则获得轻量级锁。当其他线程来抢占锁的时候,也会通过CAS修改mark owrd中指针的指向,如果失败,会通过自旋进行多次CAS(JDK1.6前默认10次,之后会自适应,如果上一次CAS操作成功,这次次数会增加,失败的话,则会减少。多次CAS会占用CPU资源,带来开销)。如果成功则获得轻量级锁,一直CAS失败的话,锁会升级为重量级锁。
重量级锁的抢占:锁对象会有一个对象监视器monitor,monitor中有_owner、_WaitSet、_EntryList,分别代表指向获得锁的线程、等待队列、同步队列。线程抢占重量级锁的时候,通过monitorenter和monitorexit设置了内存屏障。没有抢占到锁的线程进入BLOCKED状态。