synchronized是Java中的关键字,它用于实现同步访问共享资源,可解决共享资源竞争问题,以确保多个线程之间共享资源访问的正确性。当一个方法或代码块被声明为synchronized时,只有一个线程可以执行该方法或代码块。其他尝试访问该方法或代码块的线程将会被阻塞,直到当前线程执行完该方法或代码块。需要说明的是,synchronized关键字可能会导致线程阻塞和性能下降。因此,在使用synchronized关键字时,应该仔细考虑同步访问的必要性,并尽可能地减小同步访问的范围。
synchronized 与 Java 内存模型
synchronized关键字可保证所修饰元素的原子性、可见性、有序性。
对于非原子性操作,诸如i++那样的操作,可以借助synchronized来保证整个代码块的原子性。
synchronized也可实现可见性。synchronized同步块的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中”这个规则保证。
对于有序性,syncronizd关键字通过“一个变量在同一时刻只允许一个线程对其进行lock操作”这个规则来保证。
synchronized 使用场景
根据synchronized关键字修饰元素的类型,可将其使用场景分为两类:使用syncronized修饰代码块(也称同步代码块)或使用syncronized修饰方法(也称为同步方法)。其中,使用syncronized修饰方法,又根据是否是static方法,分为使用syncronized修饰类方法和syncronized修饰对象方法。
synchronized作用于代码块
synchronized关键字作用于方法中的代码块,此时基于 synchronized 指定实例加锁。用法实例如下:
synchronized(instanceName) { // instanceName 表示指定实例/* 代码块 */
}
synchronized作用于对象方法
synchronized关键字作用于对象方法,此时基于当前对象加锁。使用模式如下:
synchronized 访问类型 返回值 methodName() {/* 方法体 */
}
synchronized作用于类方法
synchronized关键字作用于对象方法,此时基于当前类加锁。使用模式如下:
synchronized 访问类型 static 返回值 methodName() {/* 方法体 */
}
使用syncronized关键字可以保证在同一时刻仅有一个线程访问该同步代码块或同步方法。synchronized 通过下述规则保证该特性:
(1) 当多个线程同时访问synchronized同步块或同步方法时,只能有一个线程得到执行,其他线程必须等待。
(2) 当一个线程访问synchronized同步块或同步方法时,其他线程仍可同时调用object中其它非synchronized同步块或方法。
(3) 当一个线程访问synchronized同步块或同步方法时,它会获得整个object的对象锁或类锁。因此,其他线程对于object的所有同步块或同步方法的访问都将被暂时阻塞,直至当前线程执行完这个同步块或同步方法。
(4) 当线程完成synchronized同步块或同步方法访问时,它会释放整个object的对象锁或类锁。因此,需要调用object上同步块或同步方法的其他线程将尝试获取这个object的对象锁。
synchronized 原理
synchronized 本质使用 Java 内置的 Monitor(管程、监视器)Object 实现线程同步。对同步代码块和同步方法,使用不同的实现方式。对于同步代码块,则是使用 monitorenter 和 monitorexit 指令 实现的;而同步方法,则是采用 ACC_SYNCHRONIZED标记符实现。
(1) 同步代码块
monitorenter 指令插入到同步代码块的开始位置,monitorexit 指令插入到同步代码块的结束位置,JVM则保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,该对象将处于锁定状态。
根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取对象的锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1,相应的,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁就被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。
注意:(a)首先,synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;(b)其次,同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。
(2) 同步方法
当某个线程要访问该方法时,会检查该方法是否有 ACC_SYNCHRONIZED 标志,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。
Java 实现 Monitor Object简介
不同虚拟机,对Monitor Object的实现略有不同。这里以HotSpot为例,简单介绍monitor的实现(基于ObjectMonitor实现)。ObjectMonitor的主要数据结构如下:
ObjectMonitor() {_header = NULL;_count = 0; // 记录个数_waiters = 0,_recursions = 0;_object = NULL;_owner = NULL; // ObjectMonitor的持有者_WaitSet = NULL; // 处于wait状态的线程,会被加入到_WaitSet_WaitSetLock = 0 ;_Responsible = NULL ;_succ = NULL ;_cxq = NULL ;FreeNext = NULL ;_EntryList = NULL ; // 处于等待锁block状态的线程,会被加入到该列表_SpinFreq = 0 ;_SpinClock = 0 ;OwnerIsThread = 0 ;}
可以看到,ObjectMonitor中有两个队列,_WaitSet 和_EntryList,用来保存ObjectWaiter对象列表(等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程。当多个线程同时访问一段同步代码时,首先会进入_EntryList 集合,当线程获取到对象的 monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1;若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。
参考
《Java编程思想》(第四版) Bruce Eckel [译]陈昊鹏
《java并发编程实战》 Brian Goetz 等著 童云兰 等译
https://www.baidu.com/ 百度AI搜索
https://blog.csdn.net/chenssy/article/details/54883355 【死磕Java并发】-----深入分析synchronized的实现原理
https://www.hollischuang.com/archives/1883 深入理解多线程(一)——Synchronized的实现原理
https://blog.csdn.net/javazejian/article/details/72828483 深入理解Java并发之synchronized实现原理
http://www.cnblogs.com/paddix/p/5367116.html Java并发编程:Synchronized及其实现原理
https://blog.csdn.net/qq_42080839/article/details/109848719 Java并发深度总结:synchronized 关键字