子类同步方法的同步性
今天看到synchronized关键字的内容,讲到了当子类继承有同步方法的父类时,继承过来的方法是否仍然是同步的?当然,提到方法继承就必须提到方法重写,重写过后的方法是否仍然具有同步性,我找了一些别人的检测代码。
下面是一个父类Super和3个子类(Sub1、Sub2、Sub3),不要看着代码很多,其实同步方法里面只有sleep方法和一些日志打印。这里列出了三种情况,来检测多线程调用一个子类对象的继承方法时,是否仍然是同步的。
- 不重写父类的同步方法,直接继承父类,休眠时间为1秒。
- 重写父类的同步方法,但不加synchronized,休眠时间为2秒。
- 重写父类的同步方法,休眠时间为3秒。
class Super {
static Logger logger = Logger.getLogger(String.valueOf(Super.class));
// 同步方法
public synchronized void testMothed() {
try {
Thread.currentThread().sleep(1000);//休眠1秒。
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info(Thread.currentThread().getName() + "," + this);
}
}
class Sub1 extends Super {
// 不重写父类的同步方法,直接继承父类,休眠时间为1秒
}
class Sub2 extends Super {
// 重写父类的同步方法,但不加synchronized,休眠时间为2秒
public void testMothed() {
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info(Thread.currentThread().getName() + "," + this);
}
}
class Sub3 extends Super{
// 重写父类的同步方法,休眠时间为3秒
public synchronized void testMothed() {
try {
Thread.sleep(3000);//休眠3秒。
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info(Thread.currentThread().getName() + "," + this);
}
}
下面给出测试代码,这里分别测试了四种情况,都是new一个对象,然后产生4条线程去执行这个对象的同步方法:
- 测试子类对象1,多线程调用sub1的同步方法,结果显示同步。
- 测试子类对象2,多线程调用sub2的同步方法,结果显示不同步。
- 测试子类对象3,多线程调用sub3的同步方法,结果显示同步。
- 测试单独的父类对象和子类对象,两个同步方法采用的锁是否一样————不一样。
public class Main {
static Logger logger = Logger.getLogger(String.valueOf(Main.class));
public static void main(String[] args) throws Exception {
final int count = 4;
// 子类对象1,多线程调用sub1的同步方法,结果显示同步
System.out.println(count+"条线程调用sub1的同步方法(直接继承自父类),每条线程sleep一秒");
final Sub1 sub1 = new Sub1();
for (int i = 0; i < count; i++) {
new Thread() {
public void run() {
sub1.testMothed();
}
}.start();
}
// 让上面所有子线程有足够时间执行结束
TimeUnit.SECONDS.sleep(count+1);
logger.info("===============================================");
// 子类对象2,多线程调用sub2的同步方法,结果显示不同步
System.out.println(count+"条线程调用sub2的非同步方法(去掉synchronized关键字),每条线程sleep两秒(改写父类方法)");
final Sub2 sub2 = new Sub2();
for (int i = 0; i < count; i++) {
new Thread() {
public void run() {
sub2.testMothed();
}
}.start();
}
// 让上面所有子线程有足够时间执行结束
TimeUnit.SECONDS.sleep(9);
logger.info("===============================================");
// 子类对象3,多线程调用sub3的同步方法,结果显示同步
final Sub3 sub3 = new Sub3();
System.out.println(count+"条线程调用sub3的同步方法,每条线程sleep三秒(改写父类方法)");
for (int i = 0; i < count; i++) {
new Thread() {
public void run() {
sub3.testMothed();
}
}.start();
}
// 让上面所有子线程有足够时间执行结束
TimeUnit.SECONDS.sleep(13);
logger.info("===============================================");
// 测试单独的父类对象和子类对象,两个同步方法采用的锁是否一样————不一样
System.out.println("测试单独的父类对象和子类对象,两个同步方法采用的锁是否一样");
Super sp = new Super();
Sub3 sub33 = new Sub3();
for (int i=0;i<count;++i){
new Thread(new Runnable() {
@Override
public void run() {
sp.testMothed();
}
}).start();
}
for (int i=0;i<count;++i){
new Thread(new Runnable() {
@Override
public void run() {
sub33.testMothed();
}
}).start();
}
}
}
上面的测试程序执行之后产生的结果如下,只需要关注每条记录的产生时间和对象即可,会发现多条记录产生的时间间隔如果与线程sleep时间吻合的话(表示因为同步锁,即使一条线程在sleep,另一条线程也必须等待),这说明该情况下子类方法是同步的,反之亦然:
4条线程调用sub1的同步方法(直接继承自父类),每条线程sleep一秒
一月 03, 2020 4:01:20 下午 Super testMothed
信息: Thread-1,Sub1@2ed230e2
一月 03, 2020 4:01:21 下午 Super testMothed
信息: Thread-4,Sub1@2ed230e2
一月 03, 2020 4:01:22 下午 Super testMothed
信息: Thread-3,Sub1@2ed230e2
一月 03, 2020 4:01:23 下午 Super testMothed
信息: Thread-2,Sub1@2ed230e2
一月 03, 2020 4:01:24 下午 Main main
信息: ===============================================
4条线程调用sub2的非同步方法(去掉synchronized关键字),每条线程sleep两秒(改写父类方法)
一月 03, 2020 4:01:26 下午 Sub2 testMothed
信息: Thread-5,Sub2@5309db31
一月 03, 2020 4:01:26 下午 Sub2 testMothed
信息: Thread-6,Sub2@5309db31
一月 03, 2020 4:01:26 下午 Sub2 testMothed
信息: Thread-8,Sub2@5309db31
一月 03, 2020 4:01:26 下午 Sub2 testMothed
信息: Thread-7,Sub2@5309db31
一月 03, 2020 4:01:33 下午 Main main
信息: ===============================================
4条线程调用sub3的同步方法,每条线程sleep三秒(改写父类方法)
一月 03, 2020 4:01:36 下午 Sub3 testMothed
信息: Thread-9,Sub3@4f640dd2
一月 03, 2020 4:01:39 下午 Sub3 testMothed
信息: Thread-11,Sub3@4f640dd2
一月 03, 2020 4:01:42 下午 Sub3 testMothed
信息: Thread-12,Sub3@4f640dd2
一月 03, 2020 4:01:45 下午 Sub3 testMothed
信息: Thread-10,Sub3@4f640dd2
一月 03, 2020 4:01:46 下午 Main main
信息: ===============================================
测试单独的父类对象和子类对象,两个同步方法采用的锁是否一样
一月 03, 2020 4:01:47 下午 Super testMothed
信息: Thread-13,Super@4e8cd4f9
一月 03, 2020 4:01:48 下午 Super testMothed
信息: Thread-15,Super@4e8cd4f9
一月 03, 2020 4:01:49 下午 Sub3 testMothed
信息: Thread-17,Sub3@37e73fb
一月 03, 2020 4:01:49 下午 Super testMothed
信息: Thread-16,Super@4e8cd4f9
一月 03, 2020 4:01:50 下午 Super testMothed
信息: Thread-14,Super@4e8cd4f9
一月 03, 2020 4:01:52 下午 Sub3 testMothed
信息: Thread-20,Sub3@37e73fb
一月 03, 2020 4:01:55 下午 Sub3 testMothed
信息: Thread-19,Sub3@37e73fb
一月 03, 2020 4:01:58 下午 Sub3 testMothed
信息: Thread-18,Sub3@37e73fb
子类同步方法中调用父类方法
《java并发编程实战》中描述了此种情况,给出了代码如下,说明了:LoggingWidget调用doSomething时获取到的锁和super.doSomething获取到的是同一个锁。这就是java中的重入锁机制。
重入锁的一种实现方法是为每一个锁关联一个计数器和一个所有者线程,当计数器为0时,就认为锁没有被任何线程所获取,当线程请求一个未被持有的锁(技术值为0)时,JVM将记下锁的持有者,并将技术值+1.。
public class Widget {
public synchronized void doSomething() {
...
}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();
}
}
下面是寻找到的验证代码,可以证明子类同步方法中调用父类方法时它们获取到的锁时一样的:
public class Test {
public static void main(String[] args) throws InterruptedException {
final TestChild t = new TestChild();
new Thread(new Runnable() {
@Override
public void run() {
t.doSomething();
}
}).start();
Thread.sleep(100);
t.doSomethingElse();
}
public synchronized void doSomething() {
System.out.println("something sleepy!");
try {
Thread.sleep(1000);
System.out.println("woke up!");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class TestChild extends Test {
public void doSomething() {
super.doSomething();
}
public synchronized void doSomethingElse() {
System.out.println("something else");
}
}
}
如果不是同一个锁,super锁住了父类对象,那么另一个线程仍然可以获得子类对象的锁。按照这个假设,以下程序应该输出:
something sleepy!
something else
woke up!
但输出的是:
something sleepy!
woke up!
something else
这说明它们的锁肯定是同一对象,至于到底是子类对象还是父类对象,《java并发编程实战》中作者提到的是父类(都会获取Widget上的锁)。