在《java并发编程实践》的第二章,介绍到了“可重入锁”的概念和作用,并且指出java的内置锁synchronized就是一种可重入锁。其中提到了Widget和LogginWidget,源码如下
public class Widget { public synchronized void doSomething() { // do somethig here... } } public class LoggingWidget extends Widget { public synchronized void doSomething() { System.out.println(toString() + ": calling doSomething"); super.doSomething(); } }
子类覆写了父类的synchonized方法,然后调用父类中的方法,此时如果没有重入的锁,那么这段代码将产生死锁。由于Widget 和LoggingWidget 中的doSomething方法都是synchonized方法,因此每个doSomething方法执行前都会获取Widget 上的锁。然而如果内置锁不是可重入的,那么在调用super.doSomething()时将无法获取Widget 上的锁,因为这个锁已经被持有,从而线程将永远停顿下去,等待一个永远也无法获得的锁。
看到红色部分的字体,想必大家会觉得疑惑。 线程进入LoggingWidget.doSomething()时获取的锁不应该是LoggingWidget对象锁吗?怎会是Widget上的锁?super.doSomething()获取的究竟是哪个对象锁呢?对于喜欢追究细节的我来说,此时有了种种疑问,尤其是写完第一篇博客: java并发编程实践学习(一)java的类锁和对象锁。按照字面的意思,貌似是说:执行子类对象的同步方法时候,也会获取父对象的锁,如果不是可重入锁的话,再次调用super.doSomething()想要第二次获取Widget对象的锁,就不会成功。
这里就产生了1个问题:什么是子类对象,什么是父类对象?是不是创建子类对象,肯定会创建一个父类的对象?
首先创建一个子类对象的时候是不会创建一个父类对象的,父类对象是根本不存在的。我们可以使用反证法,假如说创建子类对象的同时会创建一个父类对象,那如果父类是抽象类,不能实例化呢?我们知道使用A a = new A()这种方式创建对象的时候,JVM会在后台给我们分配内存空间,然后调用构造函数执行初始化操作,最后返回内存空间的引用。即构造函数只是进行初始化,并不负责分配内存空间(创建对象)。所以呢其实创建子类对象的时候,JVM会为子类对象分配内存空间,并调用父类的构造函数。我们可以这样理解:创建了一个子类对象的时候,在子类对象内存中,有两份数据,一份继承自父类,一份来自子类,但是他们属于同一个对象(子类对象),只不过是java语法提供了this和super关键字来让我们能够按照需要访问这2份数据而已。这样就产生了子类和父类的概念,但实际上只有子类对象,没有父类对象。
到这里,我们也就能够理解《java并发编程实践》中的话了。
LoggingWidget widget = new LoggingWidget(); widget.doSomengthing();
接下来我们就通过例子来证明:父类对象就是子类对象,即父类的synchronized方法和子类的synchronized方法属于同一个对象。
package net.aty.lock.extend; public class BaseClass { public synchronized void doSomeThing() { System.out.println("parent class:begin.....doSomeThing"); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("parent class:end.....doSomeThing"); } }
package net.aty.lock.extend; public class ChildClass extends BaseClass { public synchronized void childMethod() { System.out.println("---child class:begin.....childMethod"); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("---child class:end.....childMethod"); } }
package net.aty.lock.extend.thread; import net.aty.lock.extend.BaseClass; public class DemoThread1 extends Thread { private BaseClass base = null; public DemoThread1(BaseClass base) { this.base = base; } @Override public void run() { base.doSomeThing(); } }
package net.aty.lock.extend.thread; import net.aty.lock.extend.ChildClass; public class DemoThread2 extends Thread { private ChildClass child = null; public DemoThread2(ChildClass child) { this.child = child; } @Override public void run() { child.childMethod(); } }
package net.aty.lock.extend; public class ChildClass extends BaseClass { public synchronized void childMethod() { System.out.println("---child class:begin.....childMethod"); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("---child class:end.....childMethod"); } }
parent class:begin.....doSomeThing
...main running...
...main running...
parent class:end.....doSomeThing
---child class:begin.....childMethod
---child class:end.....childMethod
很显然程序符合了我们的预期,的确不存在父对象,访问父类的同步方法,跟访问子类的同步方法没有什么实质性的差别,都是要获取子类对象的锁。