java 并发——synchronized
java 并发——synchronized
介绍
在平常我们开发的过程中可能会遇到线程安全性的问题,为了保证线程之间操作数据的正确性,我们第一想到的可能就是使用 synchronized 并且 synchronized 使用的位置也是很有讲究的.首先我们来先看一下什么是 synchronized ?
- 需要使得代码变为同步方法我们需要使用 synchronized 来修饰执行前会先去获取锁,执行完释放锁,执行期间其他线程会等待.
- synchronized 有使用方法修饰在方法前面和修饰代码块.
- synchronized 以性能为代价来保证数据的完整性,所以非必要勿用.
- synchronized 只适用于单 jvm 虚拟机内,如果项目是集群的那么 synchronized 将不能保证数据是安全的.
使用场景说明
如果 synchronized 修饰在实例方法上,那么当前的锁对象就是该类的实例对象.
public synchronized void test() {
//...
}
如果 synchronized 修饰在静态方法上,那么当前的锁对象就是类对象.
public static synchronized void test() {
//...
}
如果 synchronized 修饰同步代码块中是 this 那么当前锁对象还是类的实例对象.
public void test() {
synchronized(this) {
//...
}
}
如果 synchronized 修饰同步代码块中是 Class 那么当前锁对象就是类对象.
public void test() {
synchronized(synchronizedDemo.class) {
//...
}
}
如果 synchronized 修饰同步代码块中是任意一个对象那么当前锁对象就是这个对象实例.
private final Object lock = new Object(); public void test() {
synchronized(lock) {
//...
}
}
是如何实现的呢?
我们先来先写一个例子
public class SynchronizedTest {
public synchronized void test01() {
System.out.println("test01");
}
public void test02() {
synchronized (this) {
System.out.println("test02");
}
}
}
之后我们先通过 javac 来编译成 class 文件之后再 javap 查看 class 文件发现如下图
[外链图片转存失败(img-Ilzk5vPR-1567669713818)(D:\Typora\image\1567666073594.png)]
我们通过上图发现 test01 这个方法给打上了一个标识 ACC_SYNCHRONIZED 来实现的。
而我们的 test02 同步代码块使用了 monitorenter 指令插入到了代码块开始的位置,monitorexit 指令插入到代码块结束的位置。必须是成对出现的.
在上面我们对 synchronized 已经有了一个大致的认识了,但是我们继续想学习锁的知识,就必须涉及到 cas 操作和 java 对象头了.
cas 操作
cas: compare and swap.
百度百科: cas 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
简单一点来说就是 cas 有 3 个操作数,内存值 V,旧的预期值 A,要修改的新值 B。如果 A = V,那么把 B 赋值给 V,返回 V;如果 A != V,直接返回 V。所以为了提高性能 jvm 很多操作都是依赖 cas 来实现的,cas 也就是乐观锁的实现.
java 对象头
java 对象头包含两个部分
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
- Mark World: 主要用于存储自身运行时的数据哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 id、偏向时间戳等等.
- Klass Pointer: 类型指针.指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例.
对象头中的 Mark Word,synchronized 实现就用了 Mark Word 来标识对象加锁状态.下面是 32 位虚拟机头的存储结构

看了上图我们发现有好几种锁偏向锁、轻量级锁、重量级锁(synchronized)其实是 jdk1.6 中对锁的实现引入了大量的优化来减少锁操作的开销:
首先我们来看下锁的枚举下面图中会看到: 00 偏向锁、01 无锁、10 监视器锁,又叫重量级锁、11 GC标记、101 偏向锁.
锁粗化: 将多个连续的锁扩展成一个大范围的锁,用来减少频繁的互斥获取锁释放锁导致的性能消耗.
锁消除: jvm 通过检测判断一段代码中不可能存在共享数据竞争,jvm 会对这些同步锁进行锁消除。锁消除的依据是逃逸分析的数据支持。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
} public void test() {
StringBuffer sb = new StringBuffer();
sb.append("");
}
我们都知道 StringBuffer 是线程安全的,我们虽然没有显示使用锁,但是我们在使用一些 jdk 的内置 API 时,如StringBuffer、Vector、HashTable 等,这个时候会存在隐形的加锁操作。
轻量级锁: 在没有多线程竞争的情况下避免重量级互斥锁,只需要依靠一条 cas 原子指令就可以完成锁的获取及释放.

偏向锁: 目的是消除数据再无竞争情况下的同步。使用 cas 记录获取它的线程。下一次同一个线程进入则偏向该线程,无需任何同步操作.

自旋锁: 就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。怎么等待呢?执行一段无意义的循环即可(自旋).
适应自旋锁: jdk1.6 引入了更加聪明的自旋锁,即自适应自旋锁。所谓自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。它怎么做呢?线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。
重量级锁
经过 jdk 的优化所以加锁流程是:偏向锁——>轻量级锁——>重量级锁 并且锁只能升级膨胀并不能降级.
总结
这次主要说了 synchronized 原理以及 jdk 对 synchronized 的优化。简单来说解决三种场景:
1)只有一个线程进入临界区,偏向锁.
2)多个线程交替进入临界区,轻量级锁.
3)多线程同时进入临界区,重量级锁.
就好比是你在周末一个人在公司公司突然肚子痛要上厕所,这个时候不需要等待也随便你关不关厕所门就可以上厕所(偏向锁)哈哈哈.但是在工作日的话你去上厕所发现测试门被关闭了这个时候你选择站在门口等待一会(自旋锁).之后你等待了 5 分钟后发现没什么希望,你就回到座位去等了(锁膨胀重量级锁).例子可能举的不是那么明确,但是也只能想到这样了.感谢观看!
java 并发——synchronized的更多相关文章
- Java并发——synchronized关键字
前言: 只要涉及到Java并发那么我们就会考虑线程安全,实际上能够实现线程安全的方法很多,今天先介绍一下synchronized关键字,主要从使用,原理介绍 一.synchronized的使用方法 1 ...
- Java并发-Synchronized关键字
一.多线程下的i++操作的并发问题 package passtra; public class SynchronizedDemo implements Runnable{ private static ...
- Java并发--synchronized
以下是本文的目录大纲: 一.什么时候会出现线程安全问题? 二.如何解决线程安全问题? 三.synchronized同步方法或者同步块 转载原文链接:http://www.cnblogs.com/dol ...
- Java并发synchronized详解
今天和大家一起学习下并发编程,先举一个简单的生活例子,我们去医院或者银行排队叫号,那每个工作人员之间如何保证不会叫重号呢? public class TicketDemo extends Thread ...
- 精通java并发-synchronized关键字和锁
目前CSDN,博客园,简书同步发表中,更多精彩欢迎访问我的gitee pages synchronized关键字和锁 示例代码 public class MyThreadTest2 { public ...
- 深入理解Java并发synchronized同步化的代码块不是this对象时的操作
本文仅仅是为了说明synchronized关键字同步的是对象不是方法,列子的确有失偏颇. 一.明确一点synchronized同步的是对象不是方法也不是代码块 我有关synchronized同步的是 ...
- java 并发synchronized使用
从版本1.0开始,java中每个对象都有一个内部锁,如果一个方法用synchronized修饰,那么对象的锁将保护整个方法,也就是说要调用该方法,线程必须获得内部的对象锁 换句话说 public sy ...
- Java并发——synchronized和ReentrantLock的联系与区别
0 前言 本文通过使用synchronized以及Lock分别完成"生产消费场景",再引出两种锁机制的关系和区别,以及一些关于锁的知识点. 本文原创,转载请注明出处:http:// ...
- Java并发—synchronized关键字
synchronized关键字的作用是线程同步,而线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. synchronized用法 1. 在需要同步的方法的方法签名中加入synchro ...
随机推荐
- SAP内表查询速度优化实例-OPEN SQL
一.FOR ALL ENTRIES IN 案例 今天碰到工单报工统计分析表查询速度特别慢 经查看源代码: SELECT afpo~dwerk afko~aufnr afpo~matnr AS plnb ...
- Ubuntu安装护眼程序
目录 1.安装 2.配置 参考资料 一开始想在Ubuntu下安装在Windows下使用的f.lux,但是折腾了很久f.lux也没能正常运作.于是打开另一台Ubuntu电脑,将上面使用的Redshift ...
- poj3252 Round Numbers(数位dp)
题目传送门 Round Numbers Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 16439 Accepted: 6 ...
- 在docker中使用composer install
服务器上docker中没有装composer,只有项目中有composer.phar文件,但是又需要composer来管理依赖,我才接触docker 和 php的composer,希望把解决这个问题的 ...
- Adapter的实现
Adapter概念: Adapter是连接后端数据和前端显示的适配接口,是数据和UI(View)之间一个重要的纽带.在常见的View(ListView, GridView)等地方都需要用到Adapte ...
- Codeforces 1195E. OpenStreetMap (单调队列)
题意:给出一个n*m的矩形.询问矩形上所有的a*b的小矩形的最小值之和. 解法:我们先对每一行用单调栈维护c[i][j]代表从原数组的mp[i][j]到mp[i][j+b-1]的最小值(具体维护方法是 ...
- jquery-ui拖拽对齐线位置不对的操作
1,在draggable的drag中直接获取$(this).offset()来给对齐线设置top和left: 2,在draggable的drag中直接获取event的clientX去和event的of ...
- 算法 按照ASII码从小到大输出字符(数量最多)
题目描述 如果统计的个数相同,则按照ASCII码由小到大排序输出 .如果有其他字符,则对这些字符不用进行统计. 实现以下接口:输入一个字符串,对字符中的各个英文字符,数字,空格进行统计(可反复调用)按 ...
- 1.MVC的初步了解
1.MVC简单介绍 1)原理(如图)如果想更加深入了解原理,可以访问此地址(https://www.cnblogs.com/uicodeintoworld/p/8950877.html) 理解:客户端 ...
- Python文件读写(一)
import time as t from os import path def createFile(dest): date = t.localtime(t.time()) name = '%d_% ...