synchronized关键字和内置锁理解

来源:互联网 发布:今晚非农数据最新消息 编辑:程序博客网 时间:2024/06/05 05:54

在学习《JAVA并发编程实战》 2.3.2 重入的时候,有一段介绍不是特别理解,后来查看资料有了一些理解,把自己的理解写在下面,也方便自己以后查看

在介绍重入的时候,书中有这样一段代码和介绍

class Widget {    public synchronized void doSomething() {    }}class LoggingWidget extends Widget {    public synchronized void doSomething() {        System.out.println(toString() + ": calling doSomething");        super.doSomething();    }}

由于Widget和LoggingWidget中doSomething方法都是synchronized方法,因此每个doSomthing方法在执行前都会获得Widget上的锁。然而,如果内置锁是不可重入的,那么调用super.doSomething时将无法获得Widget上的锁。

我一开始的理解是:
LoggingWidget和Widget为父子类关系,应该各自有一把锁。如果不是一把锁为什么能重入呢?不是应该先获得LoggingWidget的锁,再去获得Widget的锁?
难道是说LoggingWidget继承父类以后,在LoggingWidget中集成了父类的方法,所以只有LoggingWidget一把锁?

后来经过查阅以后,正确的姿势应该是:
LoggingWidget和Widget上是在方法上加的synchronized关键字,为对象锁,等同于

synchronized(this){XXX}

而关键点在于,锁的持有人并不是LoggingWidget类或者Widget类,而是this这个关键字,即是调用这个方法的对象。(比如 LoggingWidget lw = new LoggingWidget(),那么锁的持有人为lw,这个锁锁住的是对象实例,没有父类子类的区别)
因为调用doSomething()和super.doSomething()为同一个对象。所以需要同一把锁才能进入。

下面通过一段完整的代码来模拟一下

class Widget {    public synchronized void doSomething() {        System.out.println(Thread.currentThread().getName() + ": calling Widget doSomething");        NonreentrantDeadlock.countDown(2); // 线程休眠3秒        System.out.println(Thread.currentThread().getName() + ": leave Widget doSomething");    }}class LoggingWidget extends Widget implements Runnable{    public synchronized void doSomething() {        System.out.println(Thread.currentThread().getName() + ": calling LoggingWidget doSomething and get Lock");        NonreentrantDeadlock.countDown(2); // 线程休眠3秒        super.doSomething();        System.out.println(Thread.currentThread().getName() + ": leave LoggingWidget doSomething");    }    @Override    public void run() {        doSomething();    }}public class NonreentrantDeadlock {    public static void main(String[] args) {        LoggingWidget lw = new LoggingWidget();        Thread t1 = new Thread(lw,"乌龟"); // 创建叫乌龟的线程,传入lw        Thread t2 = new Thread(lw,"兔子"); // 创建叫兔子的线程,传入lw        t1.start(); // 启动线程        t2.start();    }    public static void countDown(int num) {        while (num > 0) {            try {                Thread.sleep(1000);                System.out.println("倒计时:" + num + "s");                num--;            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println("倒计时结束");    }}

运行结果
**兔子: calling LoggingWidget doSomething and get Lock
倒计时:2s
倒计时:1s
兔子: calling Widget doSomething
倒计时:2s
倒计时:1s
兔子: leave Widget doSomething
兔子: leave LoggingWidget doSomething
乌龟: calling LoggingWidget doSomething and get Lock
倒计时:2s
倒计时:1s
乌龟: calling Widget doSomething
倒计时:2s
倒计时:1s
乌龟: leave Widget doSomething
乌龟: leave LoggingWidget doSomething**

首先我们创建了两个线程乌龟(t1)和兔子(t2),传入同一个对象(lw),而且synchronized都标记在方法上。所所以乌龟和兔子调用的所有同步方法只有一把锁。锁的持有者为lw。
首先兔子抢到了先机,先进入了LoggingWidget的doSomething()方法,他发现锁在方法上,所以持有者为lw,而且lw的锁还在,并获得了lw的锁。
此时乌龟起步太慢,因为他和兔子都为同一个对象实例他想要进入代码段也需要lw得锁,到了doSomething()方法的时候发现锁被拿走了,只能在门口等着。
之后兔子运行到super.doSomething()的时候,发现这个方法也需要lw的锁,而它正好有这把锁,所以他就直接进入了方法。
运行完成后,兔子离开了LoggingWidget方法,并把锁还了回去。
此时乌龟还在门口等待,看到锁被还回来了,马上获得了锁,然后进入方法内部并执行。

如果我们修改一下代码。

    LoggingWidget lw1 = new LoggingWidget(); // 一个LoggingWidget实例    LoggingWidget lw2 = new LoggingWidget(); // 另一个LoggingWidget实例    Thread t1 = new Thread(lw1,"乌龟"); // 创建叫乌龟的线程,传入lw1    Thread t2 = new Thread(lw2,"兔子"); // 创建叫兔子的线程,传入lw2    t1.start(); // 启动线程    t2.start();

那么运行结果为

兔子: calling LoggingWidget doSomething and get Lock
乌龟: calling LoggingWidget doSomething and get Lock
倒计时:2s
倒计时:2s
倒计时:1s
兔子: calling Widget doSomething
倒计时:1s
乌龟: calling Widget doSomething
倒计时:2s
倒计时:2s
倒计时:1s
兔子: leave Widget doSomething
兔子: leave LoggingWidget doSomething
倒计时:1s
乌龟: leave Widget doSomething
乌龟: leave LoggingWidget doSomething

我们发现还是兔子先拿到了锁,但是乌龟同时也拿到了锁。
这是因为,两个线程传入了两个不同的对象lw1和lw2。两个不同对象实例拥有两把不同的锁,即使调用的方法相同,需要的锁也不同。
兔子先进入LoggingWidget的doSomething方法,需要lw1的锁,发现lw1的锁还在,所以它获得了锁,继续执行。
乌龟后来也到了LoggingWidget的doSomething方法,它需要lw2的锁,所以它也能够继续执行。

所以,锁的持有者和方法,类没有关系,重点在于这个锁的持有者是谁。

可以后续思考一下如果这样,锁的持有者是谁呢?

synchronized(LoggingWidget.class) {}
阅读全文
1 0
原创粉丝点击