Java中synchronized关键字你知道多少
1.什么是synchronized
我们将其理解为同步锁,可以实现共享资源的同步访问,解决线程并发的安全问题。synchronize翻译成中文:同步,使同步。synchronized:已同步。
1.1 怎么使用的
- 修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!
2.早期的synchronized
JDK1.6之前属于重量级锁,依赖于操作系统的Mutex Lock,Java的线程映射到操作系统的原生线程,需要操作系统申请互斥量,操作系统对线程的切换,需要从用户态切换到内核态,比较耗时,效率底下。
3.对synchronized的优化
JDK1.6之后在JVM层面对synchronized底层做了很多的优化,包括偏向锁,轻量级锁,自旋锁,自适应自旋锁,锁消除,锁粗化等优化技术。
3.1 偏向锁
目的:在没有线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量的开销,提升性能。特点:
- 在没有锁竞争的情况下,会把整个锁消除
- 偏向于第一个获取到偏向锁的线程
- 如果在接下来的执行中偏向锁没有被其他线程获取,那么拥有该锁的线程就不需要同步
变化:在锁竞争激烈的场合,偏向锁失效。原因是,在此情况下,极有可能每次申请锁的线程不是同一个线程,所以此时不应该使用偏向锁,否则得不偿失。But,偏向锁失效后,并不会立即膨胀为重量级锁,而是首先升级为轻量级锁。关于偏向锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。
3.2 轻量级锁
当偏向锁失效,JVM不会立即升级为重量级锁,而是试图使用轻量级锁的优化手段(JDK1.6之后加入的)。轻量级锁不是为了替代重量级锁,它的本意是是在没有线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量的开销,提升性能。
目的:和偏向锁一样
特点:
- 和偏向锁不同,轻量级锁使用CAS操作代替重量级锁。
- 使用轻量级锁,不需要申请互斥量。
- 轻量级锁的加锁和释放锁都是CAS操作。
变化:对于大多数锁来说,在整个同步周期都不存在竞争,这来自经验数据。如果没有竞争,轻量级锁使用CAS操作,避免了使用互斥锁的开销。如果存在竞争,除了互斥锁的开销,还会有额外的CAS操作,所以如果存在锁竞争,轻量级锁比重量级锁更慢。如果竞争激烈轻量级锁会迅速膨胀为重量级锁。关于轻量级锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。
3.3 自旋锁和自适应自旋锁
轻量级锁失效后,JVM避免线程真的在操作系统层面挂起,还会进行一项成为自旋锁的优化手段。在JDK1.6之前就有这项技术了,只是他是默认关闭的,可以通过参数--XX:+UseSpinning开启。JDK1.6之后默认开启。自旋不能完全替代阻塞,因为它还要占用处理器的时间。如果锁被占用的时间短,那么自旋锁的效果就好;否则,反之。自旋等待的时间必须固定,如果超过限定的次数,仍然没有获取到锁,就挂起线程。自旋默认10次,可以使用参数--XX:PreBlockSpin修改。
3.3.1 为什么会有自旋锁
互斥同步对性能最大的影响是阻塞的实现,因为线程的挂起和恢复都需要转入内核态去完成(用户态到内核态的转换将会耗费一定的时间)。而一般线程持有锁的时间并不会太长,如果仅仅为了这一点时间而挂起或恢复线程将会得不偿失。所以JVM团队就想:"能否让后面来的请求获取锁的线程等待一会儿而不被挂起?看看持有锁的线程是否很快就会释放锁"。
目的:为了减少线程的挂起和恢复,减少带来的系统开销,引入自旋锁。
3.3.2 如何实现自旋
为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋。
3.3.3 自旋的特点
- 执行忙循环
- 自旋次数固定(默认10次)
- JDK1.6之前默认关闭,之后默认打开
- 效果的好坏依赖于锁被占用的时间的长短
3.3.4 自适应自旋锁
另外,在JDK1.6时候引入了自适应自旋锁。改进:自旋次数不是固定的。根据上次同一个锁的自旋次数和锁的拥有者的状态来确定自旋次数。JVM变得越来越聪明了。与自旋锁的区别就是自旋次数不固定。
3.4 锁消除
即使JVM正在运行,如果检测到共享数据不可能存在竞争,将会执行锁消除操作。这将会节省毫无意义的请求锁的时间。
3.5 锁粗化
原则上我们写代码,总是建议将Synchronized代码块的作用范围限制的尽量小,只在共享数据的实际作用域才进行同步,使需要同步的操作数尽量小,如果存在竞争,等待线程也会尽快拿到锁。大部分情况下,上面的原则没有问题,但是如果一些列的连续操作都对同一个对象反复加锁解锁,会带来很多不必要的性能消耗。
4.Synchronized和ReenTrantLock对比
① 两者都是可重入锁
两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API
synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
③ ReenTrantLock 比 synchronized 增加了一些高级功能
相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:
a.等待可中断;b.可实现公平锁;c.可实现选择性通知(锁可以绑定多个条件)
- ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
- ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的
ReentrantLock(boolean fair)构造方法来制定是否是公平的。 - synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。
④ 性能已不是选择标准
在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量随线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。

Java中synchronized关键字你知道多少的更多相关文章
- java中synchronized关键字分析
今天我们来分析一下java中synchronized关键字.首先来看一段java代码:(本地编译环境为mac,jdk1.8的环境) Demo.java package com.example.spri ...
- Java中synchronized关键字理解
好记性不如烂笔头~~ 并发编程中synchronized关键字的地位很重要,很多人都称它为重量级锁.利用synchronized实现同步的基础:Java中每一个对象都可以作为锁.具体表现为以下三种形式 ...
- java中synchronized关键字的用法
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法. 因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识. j ...
- Java关键字-----------------java中synchronized关键字的用法
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法. 因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识. j ...
- java中synchronized关键字基础-1
1.synchronized关键字简介 synchronized是java中的一个关键字,在中文中为同步,也被称之为'同步锁',以此来达到多线程并发访问时候的并发安全问题,可以用来修饰代码块.非静态方 ...
- Java的synchronized关键字:同步机制总结
JAVA中synchronized关键字能够作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块.搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程 ...
- 从分布式锁角度理解Java的synchronized关键字
分布式锁 分布式锁就以zookeeper为例,zookeeper是一个分布式系统的协调器,我们将其理解为一个文件系统,可以在zookeeper服务器中创建或删除文件夹或文件.设D为一个数据系统,不具备 ...
- java基础Synchronized关键字之对象锁
java中Synchronized关键字之对象锁 当有多个线程对一个共享数据进行操作时,需要注意多线程的安全问题. 多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同 ...
- 【转】java中volatile关键字的含义
java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...
随机推荐
- es6的基本用法
1. let和const <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...
- CentOS 7.2配置LAMP环境——yum版
环境:CentOS 7.2 采用putty连接 方法:采用yum安装方法 目的:搭建Apache+MySQL+PHP环境 1.安装Apache yum install httpd //默认情况下,选择 ...
- tar 命令详解(持续更新)
可以用man tar查看tar命令使用的权威解释 Main operation mode: -c: 建立压缩档案 -r:向压缩归档文件末尾追加文件 -t:查看内容 -u:更新原压缩包中的文件 -x:解 ...
- VMware里装XP 没有找到硬盘驱动器
遇到问题: 解决:要给虚拟机配上一个虚拟的硬盘驱动器.在VMWare的虚拟机配置里面给这个虚拟机增加硬盘,选IDE模式,而非SCSI,设定硬盘大小和文件名就可以了.
- 【杂谈】如何对Redis进行原子操作
什么时候需要进行需要原子操作? 很常见的例子,就是利用Redis实现分布式锁. 实现锁需要哪些条件? 我们知道要实现锁,就需要一个改变锁状态的方法.这个方法能原子地对锁的状态进行检查并修改.如果修改成 ...
- 实现一个Golang的reverse函数
Reverse函数,用来反转列表,本例子用golang实现,反转一个slice列表. 因为slice是引用类型,因此直接修改参数的值即可. func myReverse(l []string) { f ...
- 洛谷 P3811 题解
题面 利用暴力快速幂O(nlogn)会TLE掉: 所以对于求1~n的所有逆元要用递推公式: #include <bits/stdc++.h> using namespace std; ]; ...
- Log4Net 配置日志按日期和日志级别分类写入
配置效果图: 配置代码: <?xml version="1.0" encoding="utf-8" ?> <log4net> <! ...
- 解决微信小程序开发者工具输入框焦点问题
Windows10笔记本上运行微信小程序开发者工具,输入框(input,textarea)没有焦点,只能在真机调试,效率太低.后来发现是Window10对笔记本高分屏支持不好,要DPI缩放,导致兼容性 ...
- Extjs的textfield的颜色设置和出现的问题笔记
Ext.getCmp('alarmsLevelVal').setFieldStyle('background-color:#ff0000;background-p_w_picpath: none; ' ...