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. 【转】浅谈 Integer 类

    突然发现自己对Integer i = 10;这种语法不太明白,于是乎有了这篇文章,那么在讲解 Integer 之前,我们先看下面这段代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 ...

  2. Windows操作下各种工具常用快捷键

    目录 Windows快捷键 谷歌浏览器快捷键 Nodepad++快捷键 Mobaxterm快捷键 Markdown快捷键 Windows快捷键 Win + E打开文件系统 Win + L锁屏 谷歌浏览 ...

  3. Oracle 数据库用户锁定与解锁,用户锁定最大密码失败次数设置方法,ORA-28000: the account is locked问题解决方法

    转至:https://blog.csdn.net/qq_38161040/article/details/108274161 用户多次密码输入错误达到一定值就会被锁定. -- 用户锁定方法 alter ...

  4. Java:命令行参数的传入和调用

    1.传入 传入时机:执行时 格式: //编译 javac Main.java //执行并传入命令行参数 -version java Main -version 此时,参数-version就以Strin ...

  5. python面试_总结03_列表练习题

    1.列表练习题 完成下列列表相关的编程题,先运行下列的test函数,在完成每道题之后,都可以通过调用test函数检测所写函数对错. def test(got, expected): if got == ...

  6. kubernetes StatefulSet控制器

    想学习更多相关知识请看博主的个人博客地址:https://blog.huli.com/ https://blog.huli.com/ 在Kubernetes系统中,Pod的管理对象RC.Deploym ...

  7. 渗透测试之BurpSuite工具的使用介绍(三)

    若希望从更早前了解BurpSuite的介绍,请访问第二篇(渗透测试之BurpSuite工具的使用介绍(二)):https://www.cnblogs.com/zhaoyunxiang/p/160002 ...

  8. tp5三级联动的实现

    tp5三级联动的实现 首先注意这里 如果说一级菜单不选中的话 后边的二级菜单是没有数据的 这里就要用到三级联动 第一步:先把一级菜单的数据查询展示出来 所以 pid 默认等于 0 代码实例: publ ...

  9. Git 常见错误 之 error:error: src refspec main does not match any/ error: failed to push some refs to 简单解决

    错误产生的原因:Github 工程默认名为了 main 由于受到"Black Lives Matter"运动的影响,GitHub 从今年 10 月 1 日起,在该平台上创建的所有新 ...

  10. CF678F题解

    首先题意中的有撤销操作,直接李超树肯定不行,题目允许离线,所以考虑线段树分治 所以问题就变成了求一次函数最大值 这不是李超树板子吗??? 然后可以对每个节点都建立动态开点李超树,查询的时候直接从叶子节 ...