锁升级

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没有执行完成,则会发生争抢。这时候会将其升级为轻量级锁。
THread0指的是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后,必须有其它线程执行唤醒方法,才能让睡眠线程运行。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值