Synchronized实现原理

一、Synchronized同步锁

使用Synchronized关键字将一段代码用锁"锁起来",只有持有当前锁的线程才能访问。在同一时间,只能有一个线程持有锁,这样,才能保证线程的安全性。

二、Synchronized关键字的使用方法

1、Synchronized关键字使用在方法体上。(作用范围:整个方法)
public synchronized void dosth1() {
		
	}
2、Synchronized关键字代码块(作用范围:被Synchronized代码块包围的代码)

referencr-to-lock:Java对象(任何Java对象都可以当做锁)

synchronized (reference-to-lock) {
				
    (不具有原子性的代码)
}

三、Synchronized关键字中对象的使用

1、Synchronized修饰在实例方法上,使用的锁对象为this当前对象。

必须使用同一对象,这样才能保证不同线程获取的是同一把锁。

public class Demo03 {
	public static void main(String[] args) {
		//使用同一对象调用dosth1()
		Demo demo = new Demo();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				demo.dosth1();
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				demo.dosth1();
			}
		});
		
		//启动线程
		t1.start();
		t2.start();
	}
}

class Demo{
	//synchronized关键字既可以加在方法体上,也可以使用synchronized代码块
	//区别:作用的范围不一样
	public synchronized void dosth1() {
		//使用this当前对象当做锁对象
	}
	
	public void dosth2() {
		synchronized (this) {
			//使用this当前对象当做锁对象
		}
	}
}
2、Synchronized修饰在静态方法上,使用的锁对象为当前对象的Class对象。

可以使用不同的对象,但是不同对象的Class对象必须一样。

public class Demo03 {
	public static void main(String[] args) {
		//使用不同对象调用dosth1(),但必须是同一个类的不同实例
		Demo demo1 = new Demo();
		Demo demo2 = new Demo();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				demo1.dosth1();
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				demo2.dosth1();
			}
		});
		
		//启动线程
		t1.start();
		t2.start();
	}
}

class Demo{
	public synchronized void dosth1() {
		//使用当前对象的Class对象
	}
	
	public void dosth2() {
		synchronized (Demo.class) {
			//使用当前对象的Class对象
		}
	}
}
3、Synchronized修饰代码块,使用自定义的对象当做锁。

可以是任何类型的Java对象。

自定义的锁对象只能用在Synchronized代码块中。

class Demo{
	//公共的锁对象
	public static final Object lock = new Object();
	
	public void dosth2() {
		synchronized (lock) {
			//表示用Object类型当做锁
		}
	}
}

四、Synchronized实现原理

Synchronized底层使用了monitorenter指令和monitorexit指令,通过对象内部叫做监视器(monitor)来实现的,分别代表尝试获取锁和释放锁。
1、监视器(monitor)

每个对象内部有一个监视器(monitor),线程通过执行monitorenter指令尝试获取monitor所有权。当monitor被占用时就会处于锁定状态。monitor内部有三个重要的部分,一是Owner:指向持有monitor所有权(持有锁)的线程,二是EntryList:存放阻塞线程,三是WaitSet:存放等待当前资源的线程。

获取monitor的过程:

如果monitor的进入数为0,当前线程进入monitor,将进入数改为1,表示该线程持有锁。

如果线程已经占有monitor,重新再次进入时,monitor的进入数+1。

如果有其他的线程已经占有了monitor,则当前线程进入阻塞状态,直到monitor的进入数变为0,当前线程再尝试获取monitor的所有权。

2、锁升级

在Java 6之前,Synchronized的实现依靠操作系统内部的互斥锁,非常消耗系统的资源。所以,在Java 6之后,提供了三种不同的锁:偏向锁,轻量级锁,重量级锁。锁的升级、降级,就是根据不同线程对锁的竞争情况进行这三种锁之间的升级、降级。

3、偏向锁

假设只有一个线程访问资源,使用偏向锁,如果发现多一个的线程竞争资源,则将锁升级为轻量级锁。

当没有并发线程时,默认使用偏向锁。因为只有一个线程访问资源,所以在偏向锁的对象头中的Mark Word里只设置线程ID,表示这个对象偏向于当前线程。使用偏向锁是为了降低无资源竞争。

当有另外一个线程来竞争当前资源时,就要升级锁为轻量级锁。

4、轻量级锁

当有线程串行竞争资源时,使用轻量级锁,轻量级锁要比偏向锁复杂一点,它涉及加锁和解锁的过程,轻量级锁的加锁过程:

线程在进行串行竞争资源时,如果对象锁状态为无锁状态,首先在当前线程的栈帧中建立一个名为锁记录(Lock Recode)的空间,存储拷贝锁对象头中的Mark Word。拷贝成功后,尝试将锁对象的

Mark Word中的ptr_to_record指向到线程对象的Mark Word,并将Lock record里的owner指向锁对象的Mark Word。如果这个更新成功了,那么这个线程就拥有了该锁,此线程就可以执行后续代码了。如果这个更新失败了,就检查是否有另一个线程也在对该对象的锁进行加锁,如果有,就说明有多个线程在竞争该对象的锁。此时,就要升级锁为重量级锁。

5、重量级锁

多个线程竞争同一个锁对象,如果其中一个线程竞争到当前锁对象,则其余线程进入阻塞状态。重量级锁在释放锁的同时,要通知其他线程重新参与锁的竞争。

依赖于操作系统互斥锁所实现的锁。操作系统的互斥锁实现线程之间的切换,需要从用户态(用于运行用户程序)转换到核心态(用于运行操作系统程序),切换成本高,效率低。依赖于操作系统互斥锁所实现的锁,称为"重量级锁"。

五、总结

重量级锁才是真正意义上的同步锁,其余两种锁只是为了降低系统开销。只有一个线程访问资源使用偏向锁可以不用加锁、释放锁的过程,降低系统的性能消耗。线程之间串行访问资源,使用轻量级锁,可以不用使用操作系统互斥锁,也能降低系统性能消耗。但是轻量级锁不能用来代替重量级锁,在有高并发的情况下,还得使用重量级锁。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值