B4. Concurrent JVM 锁机制(synchronized)
【概述】
JVM 通过 synchronized 关键字提供锁,用于在线程同步中保证线程安全。
【synchronized 实现原理】
synchronized 可以用于代码块或者方法中,产生同步代码区域,也叫互斥区。互斥区每次只能允许一个线程进入执行同步代码或重新进入执行剩余同步代码(参考线程进入等待状态后会唤醒,然后进入阻塞状态,重新获得锁的情况)。
synchronized 通过与一个对象进行绑定,或者说对一个对象进行加锁,并产生一个监控对象(monitor object)。如下图所示:

- 多线程并发执行同步代码,首先多个线程会进入集合(Entry Set),某个时间点只有一个线程可以获得(acquire) 监控对象的锁(monitor lock), 然后执行同步代码块。
- 如果获得锁的线程执行同步代码完毕,则会释放锁并退出同步代码区域(release and exit)。
- 若获得锁的线程在执行同步代码时调用被锁住对象的 wait 方法,该线程则会释放(release)锁,并进入集合(Wait Set)。锁被释放后,其他位于集合(Entry Set)的线程即可争夺监控对象的锁(monitor lock)。通过调用对象的 notify 或 notifyAll 方法可以唤醒集合(Wait Set)中的线程,不同的是 notify 会唤醒集合(Wait Set)中的其中一个不确定的线程,notifyAll 会唤醒集合(Wait Set)中的所有线程。
- 集合(Wait Set)中的线程被唤醒后,会重新进入集合(Emtry Set)与该集合中的其他线程一起争夺监控对象的锁(monitor lock),获得锁后会继续执行 wait 方法后面的代码。
【synchronized 加锁对象】
JVM 中使用 synchronized 进行加锁的对象有两种:分别为存储于 Java 堆的实例对象和存储于方法区的类信息对象。
1). synchronized 修饰实例方法,是对该方法所属的具体实例对象加锁;
2). synchronized 修饰静态方法,是对该方法所属的类信息对象加锁;
3). synchronized 同步代码块中的参数为 Class 对象或静态对象,则对 Class 对应的类信息对象加锁;
4). synchronized 同步代码块中的参数非 Class 对象或静态对象,而为其他实例对象,则对具体的实例对象加锁。
对实例对象和类信息对象加锁的区别:
- 对实例对象进行加锁,同一时刻只有一个线程获得锁,即只有一个线程获得该实例对象的使用权,其他线程无法使用该实例对象的同步代码区域。注意:对一个实例对象进行加锁,不会影响其他实例对象中同步代码区域的执行。
- 对类信息对象进行加锁,同一时刻只有一个线程获得锁,即只有一个线程可以调用对应类的同步代码区域(包括同步静态方法、同步实例方法、同步代码块),其他线程无法调用对应类的同步代码区域。注意:对一个类信息对象进行加锁,会影响该类所有实例对象中的同步代码区域的执行。
【synchronized 代码实践】
1). 多线程并发执行同一个实例对象的方法, 没有加锁的情况:
/**
* 多线程并发执行同一个实例对象的方法, 没有加锁的情况
*/
public class Main{
public static void main(String[] args){
Command c = new Command();
int nThreads = 10;
Thread[] arr = new Thread[nThreads];
for(int n = 0; n < nThreads; n++){
arr[n] = new Thread(c);
}
for(int n = 0; n < nThreads; n++){
arr[n].start();
}
}
} class Command implements Runnable{ private int i = 0; @Override
public void run() {
add();
} public void add(){
i++;
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
从下面打印结果可以看出:在没有加锁的情况下,多线程并发执行 i++,并没有按照顺序输出,并出现了线程安全的问题。

2). 多线程并发,对实例方法进行加锁:
/**
* 多线程并发,对实例方法进行加锁
*/
public class Main{
public static void main(String[] args){
Command c = new Command();
int nThreads = 10;
for(int n = 0; n < nThreads; n++){
new Thread(c).start();
}
}
} class Command implements Runnable{ private int i = 0; @Override
public void run() {
add();
} public synchronized void add(){
i++;
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
打印结果: 对实例方法进行加锁,即对实例对象进行加锁,多线程并发执行实例对象的方法时互斥执行,结果按顺序输出,且解决了线程安全的问题。

3). 多线程并发执行实例对象的方法时,同步代码块锁住实例对象:
/**
* 多线程并发执行实例对象的方法时,同步代码块锁住实例对象
*/
public class Main{
public static void main(String[] args){
Command c = new Command();
int nThreads = 10;
for(int n = 0; n < nThreads; n++){
new Thread(c).start();
}
}
} class Command implements Runnable{ private int i = 0; @Override
public void run() {
add();
} public void add(){
synchronized(this){
i++;
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
打印结果:可以看出同步代码块锁住实例对象的效果和对实例方法的效果一样,多线程并发执行实例对象的方法时互斥执行,结果按顺序输出,且解决了线程安全的问题。实际上这两种方法都是对实例对象进行加锁,不同的是同步方法(被加锁后的方法)互斥的内容是整个方法体,代码块互斥的内容是整个代码块,相对而言后者影响较小。使用同步代码块替代同步方法,缩小了锁粒度,也是锁优化的一种方式。

4). 多线程并发,对实例对象加锁,其他线程是否可以调用该实例对象的非同步代码区域?是否可以调用该实例对象的同步代码区域?
4.1). 设计了两个方法,一个生产方法(produce),一个消费方法(consume),对生成方法进行加锁,锁住实例对象,对消费方法不加锁,看下执行情况:
public class Main{
public static void main(String[] args){
Command c = new Command();
int nThreads = 5;
for(int n = 0; n < nThreads; n++){
new Thread(c).start();
}
}
}
class Command implements Runnable{
@Override
public void run() {
produce();
consume();
}
public synchronized void produce(){
System.out.println(Thread.currentThread().getName() + " produce start");
System.out.println(Thread.currentThread().getName() + " produce finish");
}
public void consume(){
System.out.println(Thread.currentThread().getName() + " consume start");
System.out.println(Thread.currentThread().getName() + " consume finish");
}
}
打印结果:可以看出当生产方法执行的时候,消费方法也在执行。由此可见,对实例对象加锁对于调用该实例对象的非同步代码区域,是没有影响的。

4.2). 设计了两个方法,一个生产方法(produce),一个消费方法(consume),对实例对象的生成方法和消费方法同时加锁,看下执行情况:
public class Main{
public static void main(String[] args){
Command c = new Command();
int nThreads = 5;
for(int n = 0; n < nThreads; n++){
new Thread(c).start();
}
}
}
class Command implements Runnable{
@Override
public void run() {
produce();
consume();
}
public synchronized void produce(){
System.out.println(Thread.currentThread().getName() + " produce start");
System.out.println(Thread.currentThread().getName() + " produce finish");
}
public synchronized void consume(){
System.out.println(Thread.currentThread().getName() + " consume start");
System.out.println(Thread.currentThread().getName() + " consume finish");
}
}
打印结果:可以看出生产方法和消费方法互斥执行。由此可见,对实例对象加锁对于调用该实例对象的同步代码区域,是影响的。不同的线程执行同一实例对象的不同的同步方法,需要竞争同一把锁,互斥执行。

5). 多线程并发执行静态同步方法
/**
* 多线程并发执行静态同步方法
*/
public class Main{
public static void main(String[] args){
Command c = new Command();
int nThreads = 10;
for(int n = 0; n < nThreads; n++){
new Thread(c).start();
}
}
} class Command implements Runnable{ private int i = 0; @Override
public void run() {
add();
} public synchronized void add(){
i++;
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
打印结果:

6). 多线程并发,同步代码块锁住类信息对象
/**
* 多线程并发执行静态同步方法
*/
public class Main{
public static void main(String[] args){
Command c = new Command();
int nThreads = 10;
for(int n = 0; n < nThreads; n++){
new Thread(c).start();
}
}
} class Command implements Runnable{ private int i = 0; @Override
public void run() {
add();
} public void add(){
synchronized(Command.class){
i++;
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
打印结果:

7). 多线程并发,对类信息对象加锁,是否可以调用不同实例对象的同步代码区域?
7.1) 多线程并发执行不同实例对象的同步方法,不对类信息对象加锁
public class Main{
public static void main(String[] args){
int nThreads = 5;
for(int n = 0; n < nThreads; n++){
new Thread(new Command()).start();
}
}
}
class Command implements Runnable{
@Override
public void run() {
produce();
consume();
}
public synchronized void produce(){
System.out.println(Thread.currentThread().getName() + " produce start");
System.out.println(Thread.currentThread().getName() + " produce finish");
}
public synchronized void consume(){
System.out.println(Thread.currentThread().getName() + " consume start");
System.out.println(Thread.currentThread().getName() + " consume finish");
}
}
打印结果:可以看出不同实例对象并发执行同步代码块,不会互斥执行。

7.2) 多线程并发执行不同实例对象的同步方法,对类信息对象加锁
public class Main{
public static void main(String[] args){
int nThreads = 5;
for(int n = 0; n < nThreads; n++){
new Thread(new Command()).start();
}
}
}
class Command implements Runnable{
@Override
public void run() {
produce();
consume();
}
public void produce(){
synchronized(Command.class){
System.out.println(Thread.currentThread().getName() + " produce start");
System.out.println(Thread.currentThread().getName() + " produce finish");
}
}
public synchronized void consume(){
System.out.println(Thread.currentThread().getName() + " consume start");
System.out.println(Thread.currentThread().getName() + " consume finish");
}
}
打印结果:在生产方法(produce)对类信息对象进行加锁,可以看到生成方法(produce)执行时与消费方法(consume)是互斥的。

B4. Concurrent JVM 锁机制(synchronized)的更多相关文章
- [转帖]B4. Concurrent JVM 锁机制(synchronized)
B4. Concurrent JVM 锁机制(synchronized) https://www.cnblogs.com/zlxyt/p/11050346.html 挺好的 感觉这个文章写的 不过想要 ...
- [转载]深入JVM锁机制-synchronized
转自:http://blog.csdn.net/chen77716/article/details/6618779,并加上少量自己的理解 目前在Java中存在两种锁机制:synchronized和Lo ...
- 转:synchronized和LOCK的实现原理---深入JVM锁机制
JVM底层又是如何实现synchronized的? 目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug ...
- java多线程之:深入JVM锁机制2-Lock (转载)
前文(深入JVM锁机制-synchronized)分析了JVM中的synchronized实现,本文继续分析JVM中的另一种锁Lock的实现.与synchronized不同的是,Lock完全用Java ...
- 深入JVM锁机制2-Lock
前文(深入JVM锁机制-synchronized)分析了JVM中的synchronized实现,本文继续分析JVM中的另一种锁Lock的实现.与synchronized不同的是,Lock完全用Java ...
- 【转载】Java中的锁机制 synchronized & 偏向锁 & 轻量级锁 & 重量级锁 & 各自优缺点及场景 & AtomicReference
参考文章: http://blog.csdn.net/chen77716/article/details/6618779 目前在Java中存在两种锁机制:synchronized和Lock,Lock接 ...
- java 锁机制(synchronized 与 Lock)
在java中,解决同步问题,很多时候都会使用到synchronized和Lock,这两者都是在多线程并发时候常使用的锁机制. synchronized是java中的一个关键字,也就是说是java内置的 ...
- 深入JVM锁机制1-synchronized
目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Loc ...
- Java 锁机制 synchronized
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/75126630 本文出自[赵彦军的博客] 1.前言 在多线程并发编程中Synchro ...
随机推荐
- python-----用多张图片生成视频
代码如下 #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019/2/19 16:42 # @Author : xiaodai # - ...
- Python基础第八天
一.内容
- 一个tomcat部署多个应用实例总结
项目组有好几个项目需要运行,之前项目少,一个tomcat对应一个项目还能应付,但现在项目多了,要是再一个tomcat对应一个项目的话,一方面看起来很业余,一方面也加大服务器的维护难度.所以现在需要对t ...
- 【转】jenkins上配置robotframeworkride自动化脚本任务
jenkins上配置robotframeworkride自动化脚本任务 编写好的自动化脚本,集成在jenkins上进行自动运行于监控,这里采用分布式构建,在一台slave上进行任务构建与自动化脚本的运 ...
- 关于在Eclipse上运行Hadoop程序的日志输出问题
在安装由Eclipse-Hadoop-Plugin的Eclipse中, 可以直接运行Hadoop的MapReduce程序, 但是如果什么都不配置的话你发现Eclipse控制台没有任何日志输出, 这个问 ...
- 彻底解决SysFader:IEXPLORE.EXE应用程序错误
彻底解决SysFader:IEXPLORE.EXE应用程序错误 转载于 西部e网(weste.net) 最近安装了IE8浏览器玩玩,但是发现一个严重的问题,就是在访问某些页面的时候,经常会出现“ysF ...
- 【技巧】解决win10的1803版本下,无法收到1809推送、从而无法更新到1903版本的问题。
figure:first-child { margin-top: -20px; } #write ol, #write ul { position: relative; } img { max-wid ...
- 慕课网5-2编程练习:flex布局制作卡片布局案例
慕课网5-2编程练习:flex布局制作卡片布局案例 小伙伴们,学习了卡片布局,接下来我们根据效果图,也写出一个卡片布局的页面吧! 效果图如下: 任务 1.主体内容的卡片一行只能显示两个. 2.卡片与卡 ...
- [Usaco2011 Feb]Generic Cow Protests
Description Farmer John's N (1 <= N <= 100,000) cows are lined up in a row and numbered 1..N. ...
- hdu 2063 过山车 (最大匹配 匈牙利算法模板)
匈牙利算法是由匈牙利数学家Edmonds于1965年提出,因而得名.匈牙利算法是基于Hall定理中充分性证明的思想,它是部图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最 ...