Java中synchronized实现类锁的两种方式及原理解析

简介

上一篇文章《Java中synchronized实现对象锁的两种方式及原理解析》中,介绍了方法锁的两种实现方式及Synchronized的底层原理,本文将讲解synchronized的类锁的两种实现方式。

一.类锁的定义

什么是类锁
类锁指synchronize修饰的静态方法或指定锁为class对象。
类锁来自何处?

不同的线程,访问使用类锁的方法的时候,他们获取到的“锁”,其实是Class对象。因为同一个类中有且只有一个Class对象,但同一个类中可以有很多个其他对象。此时,就出现了同一个类中多个对象对Class对象使用的竞争,类锁则保证了在同一时间,只允许一个线程访问被类锁锁住的方法。

类锁有两种实现方式:
  1. synchronized加在static方法上(静态方法锁)。
  2. synchronized(*.class)代码块。

接下来我们分别来看一下类锁的两种实现形式,以及它们之间的区别。

二.静态方法锁的方式实现类锁

我们使用类锁,应该保证在同一个类中,不同对象的线程,执行同一方法时,保证多个线程串行执行此方法。则说明我们的类锁生效了,带着这个预期我们来看下,通过静态方法锁实现多个线程串行执行目标方法。

完整的代码实例:
public class ClassLock1 implements Runnable {

	// 这两个实例,声明为static,是因为要在main方法中测试,与方法锁无关
	static ClassLock1 instance1 = new ClassLock1();
	static ClassLock1 instance2 = new ClassLock1();

	// 关键: static synchronized两个关键字同时使用
	private static synchronized void method() {
		System.out.println("线程名:" + Thread.currentThread().getName() + ",运行开始");
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("线程:" + Thread.currentThread().getName() + ",运行结束");
	}

	@Override
	public void run() {
		method();
	}

	public static void main(String[] args) {
		Thread thread1 = new Thread(instance1);
		Thread thread2 = new Thread(instance2);
		thread1.start();
		thread2.start();
		try {
			//两个线程必须都执行完毕后
			thread1.join();
			thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("两个线程都已经执行完毕");
	}
}
运行结果:
线程名:Thread-1,运行开始
线程:Thread-1,运行结束
线程名:Thread-0,运行开始
线程:Thread-0,运行结束
两个线程都已经执行完毕

二.静态方法锁的实现原理:

当在静态方法上,使用synchronized关键字修饰时,就可以是想方法的同步了。但其实使用类锁的第二个关键点在,不同实例的线程,调用类中的方法时,也就是上述代码中,main方法中的两个线程thread1thread2分别对应的instance1instance2。这个代码的精髓在于,同一个类中不同的两个对象(instance1instance2)的两个线程,要控制这两个线程串行执行,则需要使用类锁。那大家可能有下面的疑问:为什么要用类锁,用对象锁会怎样?

为什么要用类锁,用对象锁会怎样?

先解释如果不加static,类锁会变成什么?如果看过前一篇文章《Java中synchronized实现对象锁的两种方式及原理解析》,你会知道:

synchronized能够保证在同一时刻最多只有一个线程执行该段代码,以保证并发安全。

但只用的synchronized修饰的方法其实是方法锁,也就是对象锁,对象锁的作用范围仅限于同一个对象内。而上述代码中,我们有两个不同的对象(instance1instance2),所以如果你把static关键字去掉,则此同步方法就成了不同对象中的方法锁,又因为方法锁是对象锁中的一种实现形式,即此时instance1instance2分别持有的是一把对象锁。对象锁的特性是,每个对象的对象锁只能锁住自己对象内的线程,而无法锁住其他的对象中的线程,这就是为何对象锁不能锁住不同对象中的线程的原因,这也是对象锁和类锁的区别。

方法锁的作用范围是,同一个对象内的不同线程,而类锁则可以控制不同对象中的不同线程。
为什么同步方法上一定要加上static才能实现类锁?

同步方法加上static关键字后,那此方法在class创建的时候,就已经初始化好了。类中所有的实例,同步使用这个方法,锁的作用范围是最大的。

如果不加static关键字,则该方法会被instance1instance2同时访问到,因为锁的范围仅仅局限在instance1instance2对象中。此时是不能实现instance1instance2两个对象所在的不同线程之间的同步。

三.synchronized(*.class)代码块方式实现类锁

类锁的第二种实现方式,它的语义是:

只要同步代码块中是*.class,不管有多少个对象调用这段代码,都表示大家共用一个对象,这就实现了不同的实例,串行的执行此代码块。
完整代码示例:
public class ClassLock2 implements Runnable {

	static ClassLock2 instance1 = new ClassLock2();
	static ClassLock2 instance2 = new ClassLock2();

	@Override
	public void run() {
		method();
	}

	private void method() {
		//关键:用*.class作为锁对象,即使在不同的实例中,也能保证线程安全
		//对比:此形式类锁,与对象锁中的同步代码块锁,只有锁对象不同,同步代码块锁中的锁对象是this,而类锁是*.class)
		synchronized (ClassLock2.class) {
			System.out.println("线程名:" + Thread.currentThread().getName() + ",运行开始");
			try {
				Thread.sleep(4000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("线程:" + Thread.currentThread().getName() + ",运行结束");
		}
	}

	public static void main(String[] args) {
		Thread thread1 = new Thread(instance1);
		Thread thread2 = new Thread(instance2);
		thread1.start();
		thread2.start();
		try {
			//两个线程必须都执行完毕后
			thread1.join();
			thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("两个线程都已经执行完毕");
	}

}
运行结果
线程名:Thread-0,运行开始
线程:Thread-0,运行结束
线程名:Thread-1,运行开始
线程:Thread-1,运行结束
两个线程都已经执行完毕

可以看出,两个线程依然是串行执行,此种方式和上一种方式的结果一致。

为什么Java的synchronized实现类锁或对象锁提供了两种方式?

这两种方式是:使用synchronized修饰方法或使用synchronized代码块。

提供两种方式的原因是因为synchronized关键字的缺陷之一就是效率低。

synchronized只有当同步方法执行完或执行时抛出异常或调用wait系列方法时,才会释放锁。若不释放锁,其他线程只能等待,所以相对相率比较低。

做任何事,提高效率的第一性原理是:

在满足要求的情况下,尽可能的少做事

所以synchronized关键字除了可以加在方法上之外,还提供了同步代码块,可以让其在方法内部使用。我们在程序编写时,应该在满足要求的情况下,尽可能的减少synchronized代码块内的代码量,因为synchronized代码块内的代码,都是串行执行的,效率自然不如多个线程并行处理效率高,所以应该将那些不需要同步的代码移动到synchronized代码块外执行,这样的效率会更高。这就是synchronized为什么提供两种方式做同步。

四.总结

本文承接上文《Java中synchronized实现对象锁的两种方式及原理解析》讲解完对象锁后,又讲解了类锁。Java中的synchronized实现的对象锁和类锁的各种实现方式已经交代清楚,以及其实现原理也做了初步的解析。

但这些只是基础部分,接下来的文章中,将重点分析7中不同情况下的synchronized是否能够保证线程安全,彻底理解synchronized关键字的作用域,这样才能不再使用同步的过程中,给自己挖坑,并且后续文章中,也是面试官常常问到场景,绝对不容错过。喜欢本文,请点赞和收藏。

  • 16
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值