Java Synchronized 锁的实现原理与应用 (偏向锁,轻量锁,重量锁)

  • 简介

    在Java SE 1.6之前,Synchronized被称为重量级锁.在SE 1.6之后进行了各种优化,就出现了偏向锁,轻量锁,目的是为了减少获得锁和释放锁带来的性能消耗.

  • Synchroized的使用(三种形式)
    (1) 对于普通同步方法,锁是当前实例对象.如下代码示例:
    解释:对于set和get方法来说,都是在方法上使用了同步关键字,所以他们是同步方法,锁的就是当前的实例对象,怎么理解了,看下面的main方法,就是这个new的实例对象.所以他们的锁对象都是synchronizedMethod 这个实例.
    private int i = 0;
    
    public synchronized void setNum (int number) {
        this.i = number;
    }
    
    public synchronized int getNum () {
        return i;
    }
    
    public static void main (String[] args) {
        // 启动两个线程调用get和set方法
        SynchronizedMethod synchronizedMethod = new SynchronizedMethod();
        new Thread(() -> {
            synchronizedMethod.setNum(5);
        },"set").start();
        new Thread(() -> {
            int num = synchronizedMethod.getNum();
            System.out.println(num);
        },"get").start();
    }

    (2) 对于静态同步方法,锁是当前类的Class对象.看代码示例:
    解释:如下两个方法都是静态同步方法.所以锁是当前类的class对象,这么理解吧,静态方法是类调用的,所以锁就是这个类对象.如下代码运行结果,只有当类的第一个静态同步方法执行完毕,第二个才能执行.

    /**
    * synchronized 静态方法
    */
    public class SynchroizedStaticMethod {
    
    private static int i = 0;
    
    public static synchronized void addNum () {
        for (;;) {
            i++;
            System.out.println(Thread.currentThread().getName()+"----"+i);
            if(i >= 100){
                break;
            }
        }
    }
    
    public static synchronized void getNum () {
        System.out.println(Thread.currentThread().getName()+"----"+i);
    }
    
    public static void main (String[] args) {
        new Thread(() -> {
            SynchroizedStaticMethod.addNum();
        },"addNum").start();
        new Thread(() -> {
            SynchroizedStaticMethod.getNum();
        },"getNum").start();
    
    }
    }

    一部分执行结果
    addNum----92
    addNum----93
    addNum----94
    addNum----95
    addNum----96
    addNum----97
    addNum----98
    addNum----99
    addNum----100
    getNum----100

  • Process finished with exit code 0
    (3) 对于同步代码块,锁就是Synchronized括号里面配置的对象.如下代码实例:
    解释:通过如下代码可以证明锁就是括号里面的对象,当两个方法是一个对象时,只能是获取到对象锁的方法 执行,但是是两个锁对象时,那么两个方法获取的就是不同的锁对象,所以结果不一样了.

    /**
     * 代码块
     */
    public class SynchroizedCodeBlock {
    
        private Object object = new Object();
    
        public void printOne () {
            synchronized (object) {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "---" + 1);
                }
            }
        }
    
        public void printTwo () {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName()+"---"+2);
            }
        }
    
        public static void main (String[] args) {
            SynchroizedCodeBlock codeBlock = new SynchroizedCodeBlock();
            new Thread(() -> {
                codeBlock.printOne();
            },"printOne").start();
            new Thread(() -> {
                codeBlock.printTwo();
            },"printTwo").start();
        }
    }

    执行结果
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printTwo---2

    Process finished with exit code 0
    改变下括号里面的对象

    public void printTwo () {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName()+"---"+2);
            }
        }

    执行结果(与第一次不一样了)
    printTwo---2
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1
    printOne---1

    Process finished with exit code 0
    3.锁在什么地方(Java 对象头)

    Synchronized用的锁是存在Java的对象头里的.如果对象时数组类型,则虚拟机用3个字宽存储对象头..Java对象头里的Mark Word里默认储存对象的HashCode.分代年龄和锁标记位

    长度 内容 说明
    32/64bit Mark Word 存储对象的hashcode或锁信息等
    32/64bit Class Metadata Address 存储对象数据类型的指针
    32/64bit Array length 数组的长度(如果当前对象时数组)

    Mark Word 的状态变化表

    4.JSE1.6对锁的优化(锁的升级与对比)

    在Java SE1.6中,锁一共有4中状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

    (1)偏向锁
    why:在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁.
    what:当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里储存偏向的线程ID,以后该线程在进入和退出同步代码块时不需要进行cas操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否储存着指向当前线程的偏向锁。如果测试成功,表示该线程获得了锁。如果测试失败,则需要在测试一下Mark Word中偏向锁的表示是否设置成1(表示当前是偏向锁):如果没有设置,则使用cas竞争锁;如果设置了,则尝试使用cas将对象头的偏向锁指向当前线程。
    偏向锁的撤销:偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其它线程尝试竞争偏向锁时,持有线程才会释放锁。偏向锁的撤销,需要等待全局安全节点(在这个时间点上没有正在执行的字节码)。
    偏向锁的升级:如果有线程来竞争偏向锁,那么就需要判断对象头的Mark Word的线程ID和当前线程ID是否一致,如果不一致说明发送了竞争,那么就需要检查拥有偏向锁的线程是否还存活;如果没有存活,那么将对象头设置为无锁状态,当前线程和其它线程都可以去竞争偏向锁;如果存活,暂停拥有偏向锁的线程,遍历栈帧信息,判断这个线程是否还要使用这个锁对象,如果还需要,就撤销偏向锁,升级为轻量锁,如果不要继续使用,标记为无锁状态,重新偏向其它线程。如果升级为轻量锁后,应该还是拥有锁的线程先去执行。
    (2) 轻量锁
    why:轻量锁是为线程竞争不是很多,每个线程的执行时间不长而准备的,因为轻量锁发生竞争时,不阻塞线程,而是采用的自旋;如果竞争时就阻塞线程,而锁很快就释放了,这个线程的状态切换也是很大的消耗。
    waht:线程在执行同步代码块前,jvm会先在当前线程的栈帧中创建一个用于存储锁记录的空间,并将对象头中的Mark Word替换为为指向锁记录的指针。如果成功,当前线程获取锁,如果失败,表示其它线程竞争锁,当前线程尝试使用自旋来获取锁。这一块其实有些绕,就是怎么判断锁这一块具体参考这篇文档
    轻量锁的解锁:轻量级解锁时,会使用cas操作将disolaced Mark Word替换回到对象头,如果成功,则表示没有发生竞争。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。过程如下图所示:

    (3) 锁的优缺点对比

    优点 缺点 使用场景
    偏向锁 加锁和解锁不需要额为的消耗,和执行非同步方法相比,仅存在纳秒级别的差距 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问的同步块场景
    轻量锁 竞争线程不会阻塞,提高了程序的响应速度 如果始终得不到锁竞争的线程,使用自旋会消耗cpu 追求响应时间,同步块执行速度非常快
    重量级锁 线程竞争不使用自旋,不消耗cpu 线程阻塞,响应时间缓慢 追求吞吐量,同步块执行速度较长

    原文地址:https://blog.51cto.com/14220760/2366218

    时间: 2024-10-13 19:43:10

    Java Synchronized 锁的实现原理与应用 (偏向锁,轻量锁,重量锁)的相关文章

    Java synchronized 关键字的实现原理

    数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令,大家可能会进一步追问:JVM底层又是如何实现synchronized的? 本文所指说的JVM是指Hotspot的6u23版本,下面首先介绍synchronized的实现: synrhronized关键字简洁.清晰.语义明确,因此即使有了Lock接口,使用的还是非常广泛.其应用层的语义是可以把任何一个非null 对象 作为"锁",当syn

    Java Synchronized 锁的实现原理详解及偏向锁-轻量锁-重量锁

    Synchronize是重量级锁吗?是互斥锁吗? 它的实现原理? 前言 线程安全是并发编程中的重要关注点,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多个线程共同操作共享数据.因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只

    JAVA synchronized关键字锁机制(中)

    synchronized 锁机制简单的用法,高效的执行效率使成为解决线程安全的首选. 下面总结其特性以及使用技巧,加深对其理解. 特性: 1. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码.       2. 当一个线程同时访问object的一个synchronized(this)同步代码块时,其它线程仍然可以访问非修饰的方法或代码块.       3. 当多个线程同时访问object的synchronized(this)同步代码

    java synchronized究竟锁住的是什么

    刚学java的时候,仅仅知道synchronized一个线程锁.能够锁住代码,可是它真的能像我想的那样,能够锁住代码吗? 在讨论之前先看一下项目中常见关于synchronized的使用方法: public synchronized void syncCurrentObject() { System.out.println(Thread.currentThread().getName()+"..start.."+"-----"+System.currentTimeMi

    Java并发机制及锁的实现原理

    Java并发编程概述 并发编程的目的是为了让程序运行得更快,但是,并不是启动更多的线程就能让程序最大限度地并发执行.在进行并发编程时,如果希望通过多线程执行任务让程序运行得更快,会面临非常多的挑战,比如上下文切换的问题.死锁的问题,以及受限于硬件和软件的资源限制问题,本章会介绍几种并发编程的挑战以及解决方案. 上下文切换 即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制.时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行

    Java Synchronized及实现原理

    Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题.从语法上讲,Synchronized总共有三种用法: (1)修饰普通方法 (2)修饰静态方法 (3)修饰代码块 首先来看一下没有使用同步的情况 public class SynchronizedTest { public void method1(){ System.out

    java并发包&amp;线程池原理分析&amp;锁的深度化

          java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中.当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制.移动.代价比较高.因此,它适合随机查找和遍历,不适合插入和删除. 2.Vector与Arra

    java中多线程模拟(多生产,多消费,Lock实现同步锁,替代synchronized同步代码块)

    import java.util.concurrent.locks.*; class DuckMsg{ int size;//烤鸭的大小 String id;//烤鸭的厂家和标号 DuckMsg(){ } DuckMsg(int size, String id){ this.size=size; this.id=id; } public String toString(){ return id + " 大小为:" + size; } } class Duck{ private int

    java synchronized类锁,对象锁详解(转载)

    觉得还不错 留个记录,转载自http://zhh9106.iteye.com/blog/2151791 在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法. 因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识. java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁.线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁.获得内置锁的唯一途径就是进入这个锁的