synchronized锁升级机制
普通对象对象头:
Mark Word与锁息息相关,后面的Klass Word与锁无关,不讨论。
Mark Word结构:
public class Syn {
static int a=0;
public static void main(String[] args)
{
Test t=new Test();
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
t.method1();
}
},"Thread1");
t1.start();
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
t.method1();
}
},"Thread2");
t2.start();
}
}
class Test
{
Object o=new Object();
public void method1()
{
synchronized(o)
{
System.out.println("method1");
method2();
}
}
public void method2()
{
synchronized(o)
{
System.out.println("method2");
}
}
}
当锁对象创建好了
对象o默认开启偏向锁,开启的时候Mark Word格式如下:(找不到合适的图片,用64位说)
前54位非常重要,记录的是持有偏向锁的线程ID,但是刚刚创建的时候没有持有的线程,就是这样了:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
也就是说,没有线程占有这把锁,但他仍然等待有线程占有它。
当Thread1执行t.method1()
此时可以看到Mark Word为:
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101
前54位是Thread1的ID,说明Thread1占有这个对象o,当代码执行到method2(),jvm会比较o的Mark Word的前54位和当前线程的ID,发现相等,正常执行。这个过程非常简单,在没有并发竞争情况下非常高效。
升级为轻量锁
当Thread2执行t.method2()的时候,分情况讨论。
如果这个时候Thread1已经执行完了(或者时间片到了进入等待状态),但是不会主动释放o的控制权。o中的线程ID仍然是Thread1的。这个时候Thread2会将o的线程ID置换为自己的线程ID,o仍然是偏向锁对象。
如果这个时候Thread1没有执行完成,则会发生争抢。这时候会将其升级为轻量级锁。
轻量锁是自旋锁,即比较并赋值。
首先,Thread1将MarkWord赋值到栈帧的锁记录空间中,并将Object的标记替换为00,使用CAS赋值对象头。简单地说,比较后两位,如果后两位是01,说明没有其他线程动过这个对象,可以放心地赋值。如果后两位是00,说明这个锁对象已经被其他线程占有了。
请求轻量锁失败的线程会进入自旋状态,即循环等待。
如果是method2调用了
会加入一个栈帧
整个函数执行完了,还原到01.
锁膨胀
当自旋数量太多,或者自旋的线程太多,轻量锁会膨胀为重量锁。将自旋的线程阻塞。
为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。
重量级锁
EntryList里面存放的是阻塞的线程,就是竞争锁对象失败的线程。
waitSet里面存放的是等待线程,就是已经取得了锁控制权以后(这一点非常非常重要!),调用wait()后的等待线程。如果没有得到锁控制权就wait,会报错。由其他线程调用了这个锁对象的notify()方法,会随机将WaitSet中的一个线程唤醒,加入Blocked中。所以唤醒以后这个线程未必立刻执行,也要参与锁竞争。notifyAll方法是将WaitSet中的所有线程唤醒,加入Blocked队列。
另外注意:调用notify/notifyAll方法的线程必须获得了锁对象的控制权,否则会报错!
public class Waitandif {
public static void main(String[] args) {
Object o=new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized(o)
{
System.out.println("我是一号线程");
try {
o.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("一号线程执行完了");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(o)
{
System.out.println("我是二号线程");
try {
o.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("二号线程执行完了");
}
}
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o)
{
o.notifyAll(); //获得了锁对象的控制权
}
}
}
我是一号线程
我是二号线程
二号线程执行完了
一号线程执行完了
wait和sleep的区别
(1)wait是对象方法,任何对象都可以调用;
sleep是Thread方法,是静态方法,只有Thread类可以用。
(2)wait会释放对象锁,sleep不会
(3)wait必须配合synchronized一起使用
(4)wait后,必须有其它线程执行唤醒方法,才能让睡眠线程运行。