java 并发——synchronized


介绍

在平常我们开发的过程中可能会遇到线程安全性的问题,为了保证线程之间操作数据的正确性,我们第一想到的可能就是使用 synchronized 并且 synchronized 使用的位置也是很有讲究的.首先我们来先看一下什么是 synchronized ?

  1. 需要使得代码变为同步方法我们需要使用 synchronized 来修饰执行前会先去获取锁,执行完释放锁,执行期间其他线程会等待.
  2. synchronized 有使用方法修饰在方法前面和修饰代码块.
  3. synchronized 以性能为代价来保证数据的完整性,所以非必要勿用.
  4. synchronized 只适用于单 jvm 虚拟机内,如果项目是集群的那么 synchronized 将不能保证数据是安全的.

使用场景说明

  1. 如果 synchronized 修饰在实例方法上,那么当前的锁对象就是该类的实例对象.

    public synchronized void test() {
    //...
    }
  2. 如果 synchronized 修饰在静态方法上,那么当前的锁对象就是类对象.

    public static synchronized void test() {
    //...
    }
  3. 如果 synchronized 修饰同步代码块中是 this 那么当前锁对象还是类的实例对象.

    public void test() {
    synchronized(this) {
    //...
    }
    }
  4. 如果 synchronized 修饰同步代码块中是 Class 那么当前锁对象就是类对象.

    public void test() {
    synchronized(synchronizedDemo.class) {
    //...
    }
    }
  5. 如果 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) |
|------------------------------------|-------------------------|
  1. Mark World: 主要用于存储自身运行时的数据哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 id、偏向时间戳等等.
  2. Klass Pointer: 类型指针.指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例.

对象头中的 Mark Word,synchronized 实现就用了 Mark Word 来标识对象加锁状态.下面是 32 位虚拟机头的存储结构

看了上图我们发现有好几种锁偏向锁、轻量级锁、重量级锁(synchronized)其实是 jdk1.6 中对锁的实现引入了大量的优化来减少锁操作的开销:

首先我们来看下锁的枚举下面图中会看到: 00 偏向锁、01 无锁、10 监视器锁,又叫重量级锁、11 GC标记、101 偏向锁.

  1. 锁粗化: 将多个连续的锁扩展成一个大范围的锁,用来减少频繁的互斥获取锁释放锁导致的性能消耗.

  2. 锁消除: 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 等,这个时候会存在隐形的加锁操作。

  3. 轻量级锁: 在没有多线程竞争的情况下避免重量级互斥锁,只需要依靠一条 cas 原子指令就可以完成锁的获取及释放.

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

  5. 自旋锁: 就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。怎么等待呢?执行一段无意义的循环即可(自旋).

  6. 适应自旋锁: jdk1.6 引入了更加聪明的自旋锁,即自适应自旋锁。所谓自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。它怎么做呢?线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。

  7. 重量级锁

经过 jdk 的优化所以加锁流程是:偏向锁——>轻量级锁——>重量级锁 并且锁只能升级膨胀并不能降级.

总结

这次主要说了 synchronized 原理以及 jdk 对 synchronized 的优化。简单来说解决三种场景:

1)只有一个线程进入临界区,偏向锁.

2)多个线程交替进入临界区,轻量级锁.

3)多线程同时进入临界区,重量级锁.

就好比是你在周末一个人在公司公司突然肚子痛要上厕所,这个时候不需要等待也随便你关不关厕所门就可以上厕所(偏向锁)哈哈哈.但是在工作日的话你去上厕所发现测试门被关闭了这个时候你选择站在门口等待一会(自旋锁).之后你等待了 5 分钟后发现没什么希望,你就回到座位去等了(锁膨胀重量级锁).例子可能举的不是那么明确,但是也只能想到这样了.感谢观看!

java 并发——synchronized的更多相关文章

  1. Java并发——synchronized关键字

    前言: 只要涉及到Java并发那么我们就会考虑线程安全,实际上能够实现线程安全的方法很多,今天先介绍一下synchronized关键字,主要从使用,原理介绍 一.synchronized的使用方法 1 ...

  2. Java并发-Synchronized关键字

    一.多线程下的i++操作的并发问题 package passtra; public class SynchronizedDemo implements Runnable{ private static ...

  3. Java并发--synchronized

    以下是本文的目录大纲: 一.什么时候会出现线程安全问题? 二.如何解决线程安全问题? 三.synchronized同步方法或者同步块 转载原文链接:http://www.cnblogs.com/dol ...

  4. Java并发synchronized详解

    今天和大家一起学习下并发编程,先举一个简单的生活例子,我们去医院或者银行排队叫号,那每个工作人员之间如何保证不会叫重号呢? public class TicketDemo extends Thread ...

  5. 精通java并发-synchronized关键字和锁

    目前CSDN,博客园,简书同步发表中,更多精彩欢迎访问我的gitee pages synchronized关键字和锁 示例代码 public class MyThreadTest2 { public ...

  6. 深入理解Java并发synchronized同步化的代码块不是this对象时的操作

    本文仅仅是为了说明synchronized关键字同步的是对象不是方法,列子的确有失偏颇. 一.明确一点synchronized同步的是对象不是方法也不是代码块  我有关synchronized同步的是 ...

  7. java 并发synchronized使用

    从版本1.0开始,java中每个对象都有一个内部锁,如果一个方法用synchronized修饰,那么对象的锁将保护整个方法,也就是说要调用该方法,线程必须获得内部的对象锁 换句话说 public sy ...

  8. Java并发——synchronized和ReentrantLock的联系与区别

    0 前言 本文通过使用synchronized以及Lock分别完成"生产消费场景",再引出两种锁机制的关系和区别,以及一些关于锁的知识点. 本文原创,转载请注明出处:http:// ...

  9. Java并发—synchronized关键字

    synchronized关键字的作用是线程同步,而线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. synchronized用法 1. 在需要同步的方法的方法签名中加入synchro ...

随机推荐

  1. 2019年RTC大会记录

    小编近期在研究webRTC点对点通信技术,怀着学习的心态参加了2019年RTC大会,对所见所闻做下记录,不对的地方还请批评指正! 这次热门的话题是5G.WebRTC.AI对图像.音视频的相关处理,思科 ...

  2. kubernetes容器集群自签TLS证书

    集群部署 1.环境规划 2.安装docker 3.自签TLS证书 4.部署Flannel网络 5.部署Etcd集群 6.创建Node节点kubeconfig文件 7.获取K8S二进制包 8.运行Mas ...

  3. JavaScript寻找最长的单词算法

    返回提供的句子中最长的单词的长度. 返回值应该是一个数字. 第一步,使用String.prototype.split()方法将字符串转换成数组 function findLongestWord(str ...

  4. Ubuntu14.04搭建Boa服务

    1. 下载 boa 源码 : https://sourceforge.net/projects/boa/ 版本:boa-0.94.13.tar.gz 2. 在Ubuntu 下解压进入 [boa-0.0 ...

  5. dotNET面试(三)

    1.简述 private. protected. public. internal 修饰符的访问权限.private : 私有成员, 在类的内部才可以访问 ,也就是类内部的函数等成员可以访问.prot ...

  6. brew install ''package卡在Updating Homebrew

    关闭自动更新: export HOMEBREW_NO_AUTO_UPDATE=true

  7. jvm主要组成部分及其作用

    1.类加载器(Class Loader):加载类文件到内存.Class loader只管加载,只要符合文件结构就加载,至于能否运行,它不负责,那是有Exectution Engine 负责的. 2.执 ...

  8. FPN在faster_rcnn中实现细节代码说明

    代码参考自:https://github.com/DetectionTeamUCAS/FPN_Tensorflow 主要分析fpn多层金字塔结构的输出如何进行预测. FPN金字塔结构插入在faster ...

  9. Jquery中的offset()和position()深入剖析(元素定位)

    先看看这两个方法的定义. offset(): 获取匹配元素在当前视口的相对偏移. 返回的对象包含两个整形属性:top 和 left.此方法只对可见元素有效. position(): 获取匹配元素相对父 ...

  10. PHP chroot() 函数

    改变根目录: <?php// Change root directorychroot("/path/to/chroot/"); // Get current director ...