synchronized详细使用技巧-类锁、对象锁、this锁、非this锁

本文详细介绍了synchronized在Java中的基础特点,如对象锁与类锁的区别,如何避免全局变量和方法的线程安全问题,以及synchronized在静态方法和内部类中的应用。通过实例解析,帮助开发者理解何时何地使用synchronized以提高代码的线程安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 欢迎大家关注我的公众号,会不定期更新一些开发与测试的一些技术文章。

 

synchronized基础特点:

synchronized锁是可重入的,且在父子类继承中同样适用;

synchronized锁在遇到异常时自动释放锁;

synchronized锁的同步化不可以继承;

1、非线程安全的高发区

1.1、 多线程下的全局变量

1.2、多线程下的未同步的方法

1.3、多线程下的未进行合理同步的方法

本篇主要介绍synchronized的使用技巧,主要针对的情况是1.2和1.3。

要点:只有共享资源的读写(比如java的读写锁,仅读读不需要独占同步,读写,写写,写读均需要独占式同步)操作才需要同步化,如果不是共享资源,则不需要进行多余的同步,这也涉及到锁的粒度问题,后面我再细聊。

synchronized是java中使用的较多的同步关键字,且一直被不断优化。使用synchronized较为简单,不像Lock那样需要手动释放锁,虽然失去了一定的灵活性,但对大多数情况下,synchronized还是明智的选择。下文会着重介绍synchronized的使用技巧。

2、synchronized修饰对象方法

2.1、当多个线程同时访问同一个对象的同步方法时,是线程安全的,注意一定是同一个对象,如果创建下图中的两个MyObject类并分别在两个线程的run方法中调用methodA方法,就是非线程安全的了,因为synchronized关键字获取的为对象锁。

如下图所示:

2.2、在同一个实例对象中,synchronized关键字仅对加了synchronized关键字的方法)会保证线程安全,而对同一个实例对象中的未加synchronized关键字方法不保证线程安全。

如下图所示:

注:在多线程情况下,同一个MyObject对象的methodA方法是线程安全的,而methodB则是非线程安全的。

2.3、在同一个实例对象中,如果多个方法都被synchronized关键字修饰,则该实例对象中的多个方法之间是同步的,需要等待上一个方法执行结束后下一个获得该对象锁的方法才可以执行,这也正说明了synchronized关键字获得是对象锁,多个方法会争夺同一个对象锁。

如下图所示:

注:方法A和方法B的执行是有序的,先获得对象锁的方法先执行,另一个方法会在该方法执行完毕再执行。

3、synchronized修饰静态方法

synchronized修饰静态方法时,持有的锁为类锁,在这种情况下,无论创建多少对象,都共同持有同一个锁即类锁。

如下图所示:

3.1、两个静态方法同时加synchronized时,会发生阻塞。

如下图所示: 

3.2、一个静态方法一个动态方法,同时加synchronized,不会发生阻塞,原因是一个是静态方法的synchronized持有的是类锁,动态方法持有的synchronized是对象锁。

如下图所示:

3.3、同步synchronized(class)和直接使用synchronized修饰静态方法效果一样,都是获得类锁,而不是对象锁。

如下图所示:

4、synchronized结合静态内部类

4.1、synchronized(this)的情况下

当多个并发线程访问同一个对象的synchronized(this)同步代码块时,各个线程是依次执行的,即该对象的同步代码块中的部分是线程同步的。synchronized同步代码块提升一定的效率。

如下图所示:

结合上图中的代码,当一个线程访问该对象的synchronized同步代码块时,其他线程仍然可以访问该对象非synchronized同步代码块中的内容。且能符合我们对线程同步的预期,即synchronized同步代码块持有当前调用对象的锁,且为同步的。

注:当一个线程访问该对象的一个synchronized(this)同步代码块时,其他线程对该对象所有其他的synchronized(this)同步代码块的访问将被阻塞,因为synchronized(this)中的this获取的是整个对象的对象锁。

如下图所示:

方法1和方法2会依次执行。同理,下图中的三个方法也是依次执行的:方法3为synchronized修饰的整个方法,而方法1和方法2为synchronized(this)同步代码块。

4.2、synchronized(非this)的情况下

使用synchronized(非this)同步代码块时,同一个实例对象可以有多个synchronized(非this)同步代码块,当synchronized(非this)中非this对象相同时,多个synchronized(非this)代码块中的代码具有阻塞性,当非this对象不同时,多个synchronized(非this)代码块分别具有同步特性,相互之间不会出现阻塞。

如下图所示:

下图中两个synchronized块持有不同的锁,故不会发生阻塞,方法A持有anString锁,方法B持有anString2锁;

下图中第一个方法持有该对象的对象锁,第二个方法持有anString锁;

注:

4.3、当多个线程同时执行synchronized(x){}同步代码块时,呈同步效果,各个synchronized(x){}块会发生阻塞;

如下图所示:

4.4、当其他线程执行x对象中的synchronized同步方法或synchronized(this)同步代码块时,呈同步效果。

如下三个图所示:

<

4.5、如果其他线程调用未加synchronized的方法时,不会发生同步。

注:字符串一般不用于synchronized的锁对象,因为string常量池会产生出乎意料的锁同步和阻塞现象。另外如果锁对象放入缓存或者map后,如果被另一个synchronized所持有,也会发生同步或者阻塞。

5 、synchronized结合内部类

本质上和上述一致,只是将判断锁对象以及使用synchronized位置变换了下而已,只要找到正确的锁对象以及共享数据,就可以轻易的对同步是否合理及是否需要改进进行判断。

参考:java多线程核心编程技术

 欢迎大家关注我的公众号,会不定期更新一些开发与测试的一些技术文章。

 

本篇完。持续更新完善中......关注有惊喜,共同学习

### Java 机制的使用技巧与最佳实践 #### 选择合适的实现 在多线程环境中,选择适合应用场景的常重要。Java 提供了多种实现方式,如 `synchronized` 关键字、`ReentrantLock` 以及更高级别的并发工具。对于大多数情况而言,推荐优先考虑内置同步机制,因为其简单易用且开销较低[^1]。 ```java // 使用 synchronized 关键字 public class Counter { private int count; public synchronized void increment() { this.count++; } } ``` #### 避免死的发生 当多个线程试图获取资源时可能会发生死现象。为了预防这种情况,在编写涉及定逻辑的时候应该遵循一定的原则,例如总是按照相同的顺序尝试获得;如果可能的话尽量缩短持有时间并尽早释放所持有的对象[^2]。 ```java private final Object lockA = new Object(); private final Object lockB = new Object(); public void safeOperation() { // 总是以相同顺序加 synchronized (lockA) { doSomethingWithA(); synchronized (lockB) { doSomethingThatRequiresBothAB(); } } } ``` #### 利用 try-with-resources 自动管理Java 7 起引入了 try-with-resources 结构来简化资源管理和异常处理过程。这同样适用于那些实现了 AutoCloseable 接口的 Lock 实现(如 ReentrantLock),从而确保即使遇到未预期错误也能及时解[^3]。 ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Resource implements AutoCloseable { private final Lock lock = new ReentrantLock(); @Override public void close() { lock.unlock(); } public void performCriticalSection() throws Exception { lock.lockInterruptibly(); // 可中断地请求 try { // 执行临界区操作... } finally { lock.close(); // 确保最终会解除定状态 } } } try (Resource resource = new Resource()) { resource.performCriticalSection(); } catch (Exception e) { System.err.println(e.getMessage()); } ``` #### 减少争用提高性能 减少不必要的竞争可以显著改善程序的整体吞吐量。可以通过缩小保护范围内的代码片段长度或将某些计算移至外部来进行优化。另外还可以采用乐观读写分离策略——即允许无冲突情况下不加任何限制地访问共享数据结构,只有检测到潜在修改才会施加重入控制措施[^4]。 ```java import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; final class Cache<K, V> { private volatile Map<K,V> map; // Volatile ensures visibility of changes across threads. private final ReadWriteLock rwl = new ReentrantReadWriteLock(); public V get(K key){ rwl.readLock().lock(); try{ return map.get(key); }finally{ rwl.readLock().unlock(); } } public void put(K key, V value){ rwl.writeLock().lock(); try{ if(map==null || !map.containsKey(key)){ // Only update the cache when necessary to minimize write locks. map.put(key,value); } }finally{ rwl.writeLock().unlock(); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值