「 代码性能优化 」作为一名Java程序员,你真的了解 synchronized 吗?(一)

简介: synchronized 是Java中的关键字,是一种同步锁,本文将详细介绍 Java 中 Synchronized 用法,感兴趣的小伙伴跟博主一起讨论下。

前言

synchronized 是Java中的关键字,是一种同步锁,本文将详细介绍 JavaSynchronized 用法,感兴趣的小伙伴跟博主一起讨论下。

什么是synchronized

前言中提到,Synchronized是一种同步锁,是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。

1、Java中锁机制的特性

1.1.互斥性(原子性)

所谓原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。但是像i++、i+=1等操作字符就不是原子性的,它们是分成读取、计算、赋值几步操作,原值在这些步骤还没完成时就可能已经被赋值了,那么最后赋值写入的数据就是脏数据,无法保证原子性。被 synchronized 修饰的类或对象的所有操作都是原子的,因为在执行操作之前必须先获得类或对象的锁,直到执行完才能释放,这中间的过程无法被中断(除了已经废弃的stop()方法),即保证了原子性。

注意!synchronized 经常会和 volatile 作比较,它们俩特性上最大的区别就在于原子性,volatile 不具备原子性。

1.2.可见性

synchronizedvolatile 都具有可见性,其中 synchronized 对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。而 volatile 的实现类似,被 volatile 修饰的变量,每当值需要修改时都会立即更新主存,主存是共享的,所有线程可见,所以确保了其他线程读取到的变量永远是最新值,保证可见性。

1.3.有序性

有序性指程序执行的顺序按照代码先后执行。

synchronizedvolatile 都具有有序性,Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized 保证了每个时刻都

只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。

1.4.重入性

synchronizedReentrantLock 都是可重入锁。当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属

于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。

2、Java中锁的分类

2.1. 对象锁

在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰

2.2. 类锁

在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁

如何使用synchronized

synchronized 的用法可以从两个维度上面分类:

1. 根据修饰对象分类

1.1 修饰代码块

synchronized(this|object) {}

synchronized(类.class) {}

1.2 修饰方法

修饰非静态方法

修饰静态方法

2. 根据获取的锁分类

2.1 获取对象锁

synchronized(this|object) {}

修饰非静态方法

2.2 获取类锁

synchronized(类.class) {}

修饰静态方法

synchronized的锁膨胀

锁解决了数据的安全性,但是同样带来了性能的下降,hotspot 虚拟机的作者经过调查发现,大部分情况下,加锁的代码不仅仅不存在多线程竞争,而且总是由同一个线程多次获得。所以基于这样一个概率,synchronized 在JDK1.6 之后做了一些优化,为了减少获得锁和释放锁来的性能开销,引入了偏向锁、轻量级锁,锁的状态根据竞争激烈的程度从低到高不断升级。总结来讲,锁有四种状态,并且会因实际情况进行膨胀升级,其膨胀方向是:无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。

1. 无锁

无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

2. 偏向锁

偏向锁是 JDK1.6 中引用的优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的性能。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。一句话总结它的作用:减少统一线程获取锁的代价

3. 轻量级锁

轻量级锁也是在 JDK1.6 中引入的新型锁机制。它不是用来替换重量级锁的,它的本意是在没有多线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。是指当锁是偏向锁,存在其它线程申请同一个锁对象时的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。也就是说这里的其它线程只是申请锁,不存在两个线程同时竞争锁,可以是一前一后地交替执行同步块。

4. 重量级锁

指的是原始的Synchronized的实现,重量级锁的特点:当同一时间有多个线程竞争锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程此时其申请锁带来的开销也就变大。重量级锁一般使用场景会在追求吞吐量,同步块或者同步方法执行时间较长的场景。

总结

以上内容从Java中锁的特性、分类及优化三方面介绍了Synchronized的知识,但并未触及底层原理,请继续关注博主,接下里会继续就Synchronized展开讨论。

下一篇文章主题

jvm中对象的结构模型、Synchronized具体使用实例


相关文章
|
1天前
|
Java 测试技术 Web App开发
《手把手教你》系列技巧篇(六十二)-java+ selenium自动化测试-RemoteWebDriver让你的代码与测试分离(远程测试)
【6月更文挑战第3天】本文介绍了在没有本地浏览器的情况下,如何使用RemoteWebDriver进行远程自动化测试。RemoteWebDriver分为客户端和服务端,客户端运行测试代码,服务端启动服务。服务端需要安装JDK、浏览器和对应的WebDriver,并启动selenium-server-standalone.jar。客户端通过URL连接到服务端,并指定预期的浏览器类型。这样,客户端的测试代码就能远程控制服务端的浏览器执行自动化测试。RemoteWebDriver的优点包括跨平台和浏览器测试、提高测试稳定性以及使测试环境和执行代码的机器分离。
16 3
|
3天前
|
存储 并行计算 安全
Java语言编写代码的技术性探讨
Java语言编写代码的技术性探讨
|
3天前
|
安全 Java API
Java语言中的代码安全性探讨
Java语言中的代码安全性探讨
|
3天前
|
安全 Java
Java的线程同步与通信:深入理解wait、notify和synchronized
Java的线程同步与通信:深入理解wait、notify和synchronized
12 0
|
5天前
|
Java C++
如何在JAVA代码中嵌入汇编
如何在JAVA代码中嵌入汇编
10 1
|
5天前
|
安全 Java 开发者
深入理解Java并发编程:线程安全与性能优化移动应用开发的未来:跨平台框架与原生操作系统的融合
【5月更文挑战第29天】在Java开发中,并发编程是一个重要的议题。随着多核处理器的普及,如何充分利用多核资源,提高程序的执行效率,同时保证数据的安全性和一致性,成为开发者必须面对的挑战。本文将从线程安全的基本概念出发,探讨Java中的线程安全问题,并介绍一些常见的解决方案,如同步机制、锁优化等。最后,我们将通过实例分析,展示如何在保证线程安全的前提下,进行性能优化。
|
5天前
|
Java Linux
Linux下如何定位最耗CPU的JAVA代码
Linux下如何定位最耗CPU的JAVA代码
13 0
|
算法 安全 Java
Java 性能优化:35个小细节,让你提升Java代码运行的效率
  代码优化,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是,吃的小虾米一多之后,鲸鱼就被喂饱了。   代码优化也是一样,如果项目着眼于尽快无BUG上线,那么此时可以抓大放小,代码的细节可以不精打细磨;但是如果有足够的时间开发、维护代码,这时候就必须考虑每个可以优化的细节了,一个一个细小的优化点累积起来,对于代码的运行效率绝对是有提升的。
218 0
|
机器学习/深度学习 算法 Java
11月27日云栖精选夜读 | Java性能优化的50个细节
在JAVA程序中,性能问题的大部分原因并不在于JAVA语言,而是程序本身。养成良好的编码习惯非常重要,能够显著地提升程序性能。 1. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面: 第一,控制资源的使用,通过线程同步来控制资源的并发访问; 第二,控制实例的产生,以达到节约资源的目的; 第三,控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。
2917 0