synchronized的使用

  synchronized是一个java中的关键字,是基于JVM层面的,用于保证java的多线程安全,它具有四大特性,可用于完全替代volatile:

  • 原子性:所谓原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
  • 可见性:可见性是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的。而volatile的实现类似,被volatile修饰的变量,每当值需要修改时都会立即更新主存,主存是共享的,所有线程可见,所以确保了其他线程读取到的变量永远是最新值,保证可见性。
  • 有序性:synchronized和volatile都具有有序性,Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。
  • 可重入性:synchronized和ReentrantLock都是可重入锁。当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,可以进入,这种情况属于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。

synchronized的四大特性保证了多线程在操作共享资源时的安全。synchronized的使用方法如下:

package com.javaBase.LineDistance;

/**
* 〈一句话功能简述〉;
* 〈功能详细描述〉
*
* @author jxx
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class TestSynchronized { static TestSynchronized testSynchronized = new TestSynchronized(); private synchronized void method1() {
System.out.println("修饰实例方法。锁为实例对象。");
} private static synchronized void method2() {
System.out.println("修饰静态方法。锁为类对象。");
} private static synchronized void method3() { synchronized (TestSynchronized.class) {
System.out.println("修饰代码块,锁对象为类对象。");
} synchronized (testSynchronized) {
System.out.println("修饰代码块,锁对象为实例对象。");
}
}
}

synchronized可以修饰实例方法,静态方法,代码块,但她的锁资源只有两种,即类锁和对象锁。当使用对象锁时,稍有不慎会出问题,且看下面的代码:

package com.javaBase.LineDistance;

/**
* 〈一句话功能简述〉;
* 〈功能详细描述〉
*
* @author jxx
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class TestSynchronized2 implements Runnable { public static int i = 0; private synchronized void add () {
i++;
} @Override
public void run() {
for (int i = 0; i < 100000; i++) {
add();
}
} public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new TestSynchronized2());
Thread t2 = new Thread(new TestSynchronized2()); t1.start();
t2.start(); t1.join();
t2.join(); System.out.println(i);
}
}

运行结果:

169816

将add方法改为:

private static synchronized void add () {
i++;
}

便可正常运行。运行出错的原因是t1,t2 new了两个实例,导致进入add方法的线程持有的不是同一个锁,因此操作共享数据时出错,但若add变为静态方法,那么add同步锁就变为了类锁,类锁始终只有一个,因此运行结果正常。

synchronized原理

  了解synchronized原理之前需要先知道java对象头的概念。

java实例对象在堆中的结构如上图,包含对象头,实例变量,填充数据。实例变量保存的是类的属性信息以及父类的属性信息。填充数据用于补齐字节数,jvm要求对象的起始字节必须为8的整数倍。java对象头存储着synchronized的锁信息,它由Mark Word 和 Class Metadata Address两部分组成,Mark Word存储对象的hashCode、锁信息或分代年龄或GC标志等信息,Class Metadata Address存储类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例。那么与synchronized密切相关的就是Mark Word,Mark Word的详细结构如下:

下面来研究下synchronized在同步方法和同步代码块两种情况下线程是如何获取锁和释放锁的。我们先对同步方法进行反编译,结果如下:

 public synchronized void add();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field i:I
3: iconst_1
4: iadd
5: putstatic #2 // Field i:I
8: return
LineNumberTable:
line 14: 0
line 15: 8

JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。

下面对同步代码块进行反编译:

 public void add();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #2 // class TestSynchronized2
2: dup
3: astore_1
4: monitorenter
5: getstatic #3 // Field i:I
8: iconst_1
9: iadd
10: putstatic #3 // Field i:I
13: aload_1
14: monitorexit
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return

从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。

synchronized优化

参见另一篇博客:多线程锁的升级(膨胀)原理

参考链接:synchronized使用及原理解析

       深入理解Java并发之synchronized实现原理   

       深入理解synchronized底层原理,一篇文章就够了!

synchronized的原理的更多相关文章

  1. synchronized实现原理

    线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据.因此为了解决这个问题,我们可能需要这样一个方案, ...

  2. 深入理解Java并发之synchronized实现原理

    深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入 ...

  3. Java并发编程原理与实战九:synchronized的原理与使用

    一.理论层面 内置锁与互斥锁 修饰普通方法.修饰静态方法.修饰代码块 package com.roocon.thread.t3; public class Sequence { private sta ...

  4. 深入理解并发编程之----synchronized实现原理

    版权声明:本文为博主原创文章,请尊重原创,未经博主允许禁止转载,保留追究权 https://blog.csdn.net/javazejian/article/details/72828483 [版权申 ...

  5. Java精通并发-synchronized关键字原理详解

    关于synchronized关键字原理其实在当时JVM的学习[https://www.cnblogs.com/webor2006/p/9595300.html]中已经剖析过了,这里从研究并发专题的角度 ...

  6. Java多线程和并发(八),synchronized底层原理

    目录 1.对象头(Mark Word) 2.对象自带的锁(Monitor) 3.自旋锁和自适应自旋锁 4.偏向锁 5.轻量级锁 6.偏向锁,轻量级锁,重量级锁联系 八.synchronized底层原理 ...

  7. synchronized实现原理及其优化-(自旋锁,偏向锁,轻量锁,重量锁)

    1.synchronized概述: synchronized修饰的方法或代码块相当于并发中的临界区,即在同一时刻jvm只允许一个线程进入执行.synchronized是通过锁机制实现同一时刻只允许一个 ...

  8. synchronized底层原理

    synchronized底层语义原理 Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现. 在 Java 语言中,同步用的最多的地方可能是被 syn ...

  9. synchronized运行原理以及优化

    线程安全问题 线程不安全: 当多线程并发访问临界资源时(可共享的对象),如果破坏原子操作,可能会造成数据不一致. 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可以保证其正确性. 原子操作 ...

随机推荐

  1. centos安装k8s集群

     准备工作 关闭swap,注释swap分区 swapoff -a 配置内核参数,将桥接的IPv4流量传递到iptables的链 cat > /etc/sysctl.d/k8s.conf < ...

  2. 可视化里程碑:可拖拽使用的可视化BI工具

    在数据量越来越大的今天,如何利用好数据,更好的为人类社会服务,成为人们所关心的话题,而其中数据可视化作为最后一个环节,也是人们最为直观的感受,自然而然备受重视.同质化的应用越来越多,应用开发者也开始在 ...

  3. 在不受支持的 Mac 上安装 macOS Monterey 12(OpenCore Patcher)

    一.介绍 本文通用于 macOS Big Sur 和 macOS Monterey,也可以视作笔者 早期文章 的升级版. 这一章节将介绍 macOS Monterey 的系统要求和不受支持的 Mac ...

  4. Lua中如何实现类似gdb的断点调试--01最小实现

    说到Lua代码调试,最常用的方法应该就是加一堆print进行打印.print大法虽好,但其缺点也是显而易见的.比如效率低下,需要修改原有函数内部代码,在每个需要的地方添加print语句,运行一次只能获 ...

  5. drawable如何修改图片大小

    这个问题刚开始遇到是导入图片太大,在网上找了许多教程大多都是采用setBounds()方法自己尝试许多次还是没成功,在经历了多达数个小时折磨后我找到两个方法1.在导入图片之前直接对图片进行修改大小.( ...

  6. Qt:QSqlDatabase

    0.说明 QSqlDatabase类处理与数据库连接相关的操作.一个QSqlDatabase实例就代表了一个连接,连接时要提供访问数据库的driver,driver继承自QSqlDriver. 通过静 ...

  7. SpringSecurity原理解析以及CSRF跨站请求伪造攻击

    SpringSecurity SpringSecurity是一个基于Spring开发的非常强大的权限验证框架,其核心功能包括: 认证 (用户登录) 授权 (此用户能够做哪些事情) 攻击防护 (防止伪造 ...

  8. 字符串格式化String.Format

    //给变量赋值字符串00002 string s = String.Format( "{0:d5}", 2);

  9. 数字逻辑实践6-> 从数字逻辑到计算机组成 | 逻辑元件总结与注意事项

    00 一些前言 数字逻辑是计算机组成与体系结构的前导课,但是在两者的衔接之间并没有那么流畅,比如对面向硬件电路的设计思路缺乏.这篇总结是在数字逻辑和计组体系结构的衔接阶段进行的. 虽然这篇文是两门课的 ...

  10. Laravel 报错: Dotenv values containing spaces must be surrounded by quotes.

    报错信息如下: 原因: .env文件配置中欧冠包含空格的配置信息,用双引号""引起来即可