【JVM第六篇】:Java-类锁和对象锁

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hxcaifly/article/details/84107044

1.类锁和对象锁的定义

对象锁的定义

是针对一个对象的,它只在该对象的某个内存位置声明一个标志位标识该对象是否拥有锁,所以它只会锁住当前的对象。一般一个对象锁是对一个非静态成员变量进行syncronized修饰,或者对一个非静态方法进行syncronized修饰。对于对象锁,不同对象访问同一个被syncronized修饰的方法的时候不会阻塞住。

类锁

类锁是锁住整个类的,当有多个线程来声明这个类的对象的时候将会被阻塞,直到拥有这个类锁的对象被销毁或者主动释放了类锁。这个时候在被阻塞住的线程被挑选出一个占有该类锁,声明该类的对象。其他线程继续被阻塞住。

注意点

  1. 无论是类锁还是对象锁,父类和子类之间是否阻塞没有直接关系。当对一个父类加了类锁,子类是不会受到影响的,相反也是如此。因为synchronized关键字并不是方法签名的一部分,它是对方法进行修饰的。当子类覆写父类中的同步方法或是接口中声明的同步方法的时候,synchronized修饰符是不会被自动继承的,所以相应的阻塞问题不会出现。
  2. 构造方法不可能是真正同步的(尽管可以在构造方法中使用同步块)。

2.如何声明类锁和对象锁

类锁的声明方式

static void myMethod(){
  synchronized(MyClass.class){
     //code
  }
}

等价于:

static synchronized void myMethod(){
  // code
}

对象锁的声明方式

void myMethod(){
  synchronized(this){
     //code
  }
}

等价于:

synchronized void myMethod(){
  // code
}

3.代码测试

如下面代码,我声明了一个类Runtest,在该类中包含无锁方法noSyn、对象锁方法 outMethod, outMethod2与一个类锁方法plus。声明了一个继承了线程Thread的类Obj,在该类中用来访问Runtest的方法,模拟各种测试场景。启动测试类是MyDemo,在该类中有两种测试方法,一种是声明同一个测试类对象而开辟多个线程,用来测试对象锁;另外一种是每当声明一个新的线程则同时声明一个新的测试类对象,用来测试类锁。

具体测试流程分为两个步骤。第一个步骤是直接运行如下代码,测试结果是用来测试对象锁的锁效果;第二个步骤是把for循环中的前两行代码注释掉,把其余三行有注释的代码删去注释,还有,类Obj最后一行注释代码删去注释,用来测试类锁的效果。

public class MyDemo{
	public static void main(String[] args) {
		Runtest runtest = new Runtest();
		for (int i = 0; i < 10; i++) {
			Thread thread = new Obj(runtest, i);// 1同一个RunTest1对象但每次有个新的线程
			thread.start();
		}
	}
}
 
class Obj extends Thread {
	Runtest r;
	int i = 0;
 
	public Obj(Runtest r, int i) {
		this.r = r;
		this.i = i;
	}
 
	public void run() {
		r.noSyn(this.getId());
       //用以测试同一个对象在不同线程中访问不同方法
       if(i % 2 == 0){
			r.outMethod2();//对象锁方法2
		}else{
			r.outMethod();//对象锁方法1
		}
       //Runtest.plus(); //类锁方法
	}
}
 
class Runtest {
	static int i = 0;
	public void noSyn(long threadId) {
		System.out.println("nosyn: class obj->" + this + ", threadId->" + threadId);
	}
 
	synchronized public void outMethod() {
		System.out.println("in outMethod begin");
		try {
			Thread.sleep(2000L);
		} catch (InterruptedException e) {
		}
		System.out.println("in outMethod end");
	}
 
	synchronized public void outMethod2() {
		System.out.println("in outMethod2 begin");
		try {
			Thread.sleep(2000L);
		} catch (InterruptedException e) {
		}
		System.out.println("in outMethod2 end");
	}
 
	public static void plus() {
		synchronized (Runtest.class) {
			System.out.println("start: " + i);
			try {
				Thread.sleep(3000L);
			} catch (InterruptedException e) {
			}
			i++;
			System.out.println("i is " + i);
		}
	}
}

执行结果
按照上述测试步骤, 对于类锁的测试结果如下:
第一部分输出:

nosyn: class obj->test.Runtest@3851ddd2, threadId->10
in outMethod2 begin
nosyn: class obj->test.Runtest@1722fe15, threadId->13
in outMethod begin
nosyn: class obj->test.Runtest@6e1b0caf, threadId->15
nosyn: class obj->test.Runtest@31ddeda2, threadId->16
in outMethod2 begin
nosyn: class obj->test.Runtest@5be9d36, threadId->14
in outMethod2 begin
in outMethod begin
nosyn: class obj->test.Runtest@1624bef5, threadId->11
in outMethod begin
nosyn: class obj->test.Runtest@1f92ee25, threadId->18
in outMethod2 begin
nosyn: class obj->test.Runtest@2543472c, threadId->17
in outMethod begin
nosyn: class obj->test.Runtest@6750cf54, threadId->19
in outMethod begin
nosyn: class obj->test.Runtest@8afcd0c, threadId->12
in outMethod2 begin
/**************************上述输出几乎同时*********/

第二部分输出:

in outMethod2 end
in outMethod end
in outMethod end
in outMethod2 end
in outMethod end
in outMethod end
in outMethod2 end
in outMethod end
in outMethod2 end
in outMethod2 end
/**************************sleep一段时间*********/

第三部分输出:

start: 0
i is 1
start: 1
i is 2
start: 2
i is 3
start: 3
i is 4
start: 4
i is 5
start: 5
i is 6
start: 6
i is 7
start: 7
i is 8
start: 8
i is 9
start: 9
i is 10

对执行结果做出解释:

  1. 它会首先执行没加锁的方法,无论是一个对象多个线程还是每个线程中一个对象,对无锁方法都是没有影响的。对于对象锁和类锁来说,只会对加了锁的方法产生不同的影响。

  2. 当多个对象对同一个加了对象锁的方法进行调用则会被阻塞,而不同对象对不同方法访问则不会被阻塞,就算加了对象锁,当同一个对象在线程1中访问一个方法,在线程2中再去访问另外一个加锁方法,则同样也会被阻塞。针对上面代码就是,在线程1中对象runTest访问outMethod,而在线程2中访问outMethod2则会被阻塞。

  3. 对于类锁,则会把整个类锁住,也就说只能有一个对象拥有当前类的锁。当一个对象拥有了类锁之后,另外一个对象还想竞争锁的话则会被阻塞。两个对象A,B,如果A正在访问一个被类锁修饰的方法function,那么B则不能访问。因为类锁只能在同一时刻被一个对象拥有。相对于对象锁,则是不同。还是A,B两个对象,如果A正在访问对象锁修饰的function,那么这个时候B也可以同时访问。

对于类锁的输出进行分析,它的输出我表示成三个部分:

在第一部分输出几乎同时输出,是因为每个线程都是一个新的对象,不同对象访问对象锁是不会被阻塞的,所以几乎是按照程序的先后输出;

第二部分输出就是两个方法中的sleep时间消耗,没有什么问题;

第三部分就是计算i++,然后输出结果,这部分输出是比较慢的。因为plus方法是类锁,在同一时刻只能是一个对象拥有该锁,所以多个线程必须顺序执行结果,所以最后i的输出也是10.

其中对于对象锁,当一个对象拥有锁之后,访问一个加了对象锁的方法,而该方法中又调用了该类中其他加了对象锁的方法,那么这个时候是不会阻塞住的。这是java通过可重入锁机制实现的。可重入锁指的是当一个对象拥有对象锁之后,可以重复获取该锁。因为synchronized块是可重入的,所以当你访问一个对象锁的方法的时候,在该方法中继续访问其他对象锁方法是不会被阻塞的。

参考
https://blog.csdn.net/tubro2017/article/details/84570543

猜你喜欢

转载自blog.csdn.net/hxcaifly/article/details/84107044