难度
初级
学习时间
30分钟
适合人群
零基础
开发语言
Java
开发环境
- JDK v11
- IntelliJ IDEA v2018.3
友情提示
- 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
- 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!
1.温故知新
在《“全栈2019”Java多线程第十六章:同步synchronized关键字详解》一章中介绍了synchronized关键字。
现在我们来讲解同步代码块/方法中的同步锁。
2.生活中的同步锁
在我们日常生活中,也有异步和同步的例子。下面,先来看一个异步的例子:如果把人看作是一个线程,把厕所看作是一个资源,人上厕所就是线程去访问资源。多个人就是多个线程,多个人上厕所就是多线程访问资源。
接下来,就展示了多个人去上同一个厕所,即多线程异步访问同一个资源:
当然了,实际生活中,我们都是遵守秩序的好同学。我们多个人会排队一个一个地上厕所,即多线程同步访问同一个资源:
例子中的同步锁是什么呢?
是门上的锁。
当进入厕所后,把门锁上,上完厕所时,把门打开。
当门被锁上后,后面的人就进不来,除非门里面的人开门,这就避免发生多个人同时上一个厕所的情况。
注意:上面例子中都是多个线程在访问同一个资源,会造成线程安全问题,所以需要同步。但是,单线程访问同一个资源或多线程访问各自的资源时,则不需要同步。
展示单线程访问同一资源情形:
展示多线程访问各自的资源情形:
3.同步对象
什么是同步对象?
在回答这个问题之前,我们需要借助同步代码块的定义来看:
可以看到的是写在紧跟synchronized关键字后面小括号里面的内容就是同步对象。
下面通过示例代码来感受一下:
上述程序代码中的同步对象就是this。
this我们知道代表的是实际调用该方法的当前对象。例如这里的ticketingTaskThread对象:
当然了,我们不光可以用this来作为同步对象,还可以使用任何一个Java对象来作为同步对象。
例如,我们这里可以使用String对象来作为同步对象:
再比如,使用Object对象来作为同步对象:
最后,我们也可以使用自定义Java对象来作为同步对象:
为什么所有Java对象都可以作为同步对象呢?
因为每个Java对象都有且只有一个同步锁,所以所有Java对象都可以作为同步对象。
接下来,就来介绍什么是同步锁。
4.同步锁的定义
每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁,当消费者线程试图执行带有synchronized(this)标记的代码块时,消费者线程必需先获得this关键字引用的Stack对象的锁。
下面,我们分词解释+举例说明同步锁的定义。
例子是上一章中售票类的示例代码:
第一句,每个Java对象都有且只有一个同步锁:
“每个Java对象都有且只有一个同步锁”中的每个Java对象也就是包含了所有Java对象。
例如String、Object、自定义Java对象...等等。在上一小节讲解同步对象时也演示了这个问题。
“每个Java对象都有且只有一个同步锁”中的有且只有一个同步锁说的是每个Java对象都有一个属于自己的同步锁,且这个同步锁只有一个。说明锁具有唯一性。
第二句,在任何时刻,最多只允许一个线程拥有这把锁:
正是因为锁的唯一性,所以只能有一个线程拥有这把锁。
就好比,三个人同时跑去洗手间,但每次只能进去一人,这个进去的人相当于拥有这把锁,进去以后呢,他会将门锁上,直到上完厕所出来,然后轮到后面要上厕所人。
“在任何时刻,最多只允许一个线程拥有这把锁”这句话用“在任何时刻,最多只允许一个人进入洗手间”理解也不错,希望大家可以理解。
第三句,当消费者线程试图执行带有synchronized(this)标记的代码块时:
“当消费者线程试图执行带有synchronized(this)标记的代码块时”中的消费者线程指的是什么?
简单来说就是每一个线程都是消费者线程,大家理解为线程即可。
“当消费者线程试图执行带有synchronized(this)标记的代码块时”中的synchronized(this)标记的代码块指的是什么?
synchronized(this)标记的代码块指的是同步代码块。
当然了,这里不应该只理解为同步代码块,毕竟还有同步方法。所以,这里综合指的是同步代码块和同步方法。
综上所述,“当消费者线程试图执行带有synchronized(this)标记的代码块时”的意思就是当线程执行同步代码块或同步方法时。
第四句,消费者线程必需先获得this关键字引用的Stack对象的锁:
将“消费者线程必需先获得this关键字引用的Stack对象的锁”中的消费者线程替换为线程,即“线程必需先获得this关键字引用的Stack对象的锁”。
“线程必需先获得this关键字引用的Stack对象的锁”中的Stack对象指的是的堆栈中的对象。
综上所述,“消费者线程必需先获得this关键字引用的Stack对象的锁”意思就是线程必需先获得同步对象的锁。
综上所述
“每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁,当消费者线程试图执行带有synchronized(this)标记的代码块时,消费者线程必需先获得this关键字引用的Stack对象的锁。”这句话说明以下几点:
- 同步需要有同步对象。
- 每个同步对象里面有且只有一个同步锁。
- 每个Java对象都可以是同步对象。
- 线程在执行同步代码块/方法时必需先获得同步锁。
- 最多只允许一个线程拥有这把同步锁。
别名
同步锁又被称为监视器。
这里大家知道就行,叫什么都对,自己喜欢叫什么就叫什么。
5.静态/非静态方法里面的同步对象
上述程序代码中同步对象写的是this,该程序运行之所以没有报错是因为我们可以在非静态方法中使用this关键字,那么如果是在静态方法中呢?来修改上述程序代码试试:
这里我们改了两处。
第一处,我们将非静态方法改为了静态方法:
第二处,因为是静态方法,所以方法内部的成员变量也必须是静态的:
好,配合之前的Main类:
运行程序,执行结果:
错误信息:
文字版:
/Users/admin/Workspace/Java/Hello/src/lab/TicketingTaskThread.java
Error:(27, 23) java: 无法从静态上下文中引用非静态 变量 this
错误出现在TicketingTaskThread类的27行:
异常信息说“无法从静态上下文中引用非静态 变量 this”,通过前面Java基础知识的学习,我们知道非静态变量(实例变量/成员变量)只能被对象调用;静态变量(类变量)可以直接被类名调用。
故,这里需要一个新的同步对象来代替,它是什么呢?
它就是类对象。即类名.class。任意类的类名都行。例如String.class、Object.class...等等。
接下来,修改我们的TicketingTaskThread类:
这里我们使用的类名是当前TicketingTaskThread类的名称。
Main类无需修改。
运行程序,执行结果:
从运行结果来看,程序没有任何问题。
综上所述
非静态方法中,同步代码块的同步对象可以是任意Java实例对象。
静态方法中,同步代码块的同步对象可以是任意Java类对象。
6.静态/非静态同步方法的同步对象
上一小节展示了静态/非静态方法里面的同步代码块中的同步对象各是什么,本小节来认识静态/非静态同步方法上的同步对象又是什么。
非静态同步方法:
非静态同步方法的同步对象是this。
静态同步方法:
静态同步方法的同步对象是当前类的类名.class。
例如上述程序代码中的静态同步方法的同步对象是TicketingTaskThread.class。
总结
- 紧跟synchronized关键字后面小括号里面的内容就是同步对象。
- 每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁,当消费者线程试图执行带有synchronized(this)标记的代码块时,消费者线程必需先获得this关键字引用的Stack对象的锁。
- 同步需要有同步对象。
- 每个同步对象里面有且只有一个同步锁。
- 每个Java对象都可以是同步对象。
- 线程在执行同步代码块/方法时必需先获得同步锁。
- 最多只允许一个线程拥有这把同步锁。
- 同步锁又被称为监视器。
- 非静态方法中,同步代码块的同步对象可以是任意Java实例对象。
- 静态方法中,同步代码块的同步对象可以是任意Java类对象。
- 非静态同步方法的同步对象是this。
- 静态同步方法的同步对象是当前类的类名.class。
至此,Java中同步锁相关内容讲解先告一段落,更多内容请持续关注。
答疑
如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。
上一章
“全栈2019”Java多线程第十六章:同步synchronized关键字详解
下一章
“全栈2019”Java多线程第十八章:同步代码块双重判断详解
学习小组
加入同步学习小组,共同交流与进步。
- 方式一:关注头条号Gorhaf,私信“Java学习小组”。
- 方式二:关注公众号Gorhaf,回复“Java学习小组”。
全栈工程师学习计划
关注我们,加入“全栈工程师学习计划”。
版权声明
原创不易,未经允许不得转载!