zoukankan      html  css  js  c++  java
  • synchronized的分析与使用

    synchronized的分析与使用


    • 同步 机制: synchronized是Java同步机制的一种实现,即互斥锁机制,它所获得的锁叫做互斥锁
    • 互斥锁: 指的是每个对象的锁一次只能分配给一个线程,同一 时间只能由一个线程占用
    • 作用: synchronized用于保证同一时刻只能由一个线程进入到临界区,同时保证共享变量的可见性、原子性和有序性
    • 使用: 当一个线程试图访问同步代码方法(块)时,它首先必须得到锁,退出或抛出异常时必须释放锁

    synchronized关键字的使用:

    1. 普通同步方法,锁是当前实例对象
    2. 静态同步方法,锁是当前类的class对象
    3. 同步方法块,锁是括号里面的对象
    代码1:
    /**
     * 一个变量类类
     * @author yang
     */
    public class NewNum {
        /**
         * 一个静态变量
         */
        static int num = 0;
    
        public static void main(String[] args) {
            for(int j = 0; j < 1000; j++) {
                SetNumThread setNumThread = new SetNumThread();
                Thread timeOne = new Thread(setNumThread);
                timeOne.start();
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(NewNum.num);
        }
    }
    
    class SetNumThread implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                NewNum.num++;                //1
                System.out.println(NewNum.num);
            }
        }
    }
    

    以上代码块的理想输出结果应当是10000,但是实际上却是每一次输出都不一样。造成这种结果,正是由于线程对共享变量的操作是不安全的。应当对共享变量num加锁。将1处代码变为:

    synchronized (SetNumThread.class){
        NewNum.num++;
    }
    

    synchroized 的实现原理

    同步代码块是使用monitorentermonitorexit指令实现的,同步方法(在这看不出来需要看JVM底层实现)依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。

    同步代码块:monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;

    同步方法:synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。

    锁优化

    jdk1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

    锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

    锁的实现机制与java对象头息息相关,锁的所有信息,都记录在java的对象头中。用2字(32位JVM中1字=32bit=4baye)存储对象头,如果是数组类型使用3字存储(还需存储数组长度)。对象头中记录了hash值、GC年龄、锁的状态、线程拥有者、类元数据的指针。

    锁

    偏向锁:

    在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程。

     那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步,退出同步也,无需每次加锁解锁都去CAS更新对象头,如果不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候需要锁膨胀为轻量级锁,才能保证线程间公平竞争锁。

    轻量级锁:

    轻量锁与偏向锁不同的是: 1. 轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁 2. 每次进入退出同步块都需要CAS更新对象头 3. 争夺轻量级锁失败时,自旋尝试抢占锁

    自旋锁:

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

    重量级锁:

    当竞争线程尝试占用轻量级锁失败多次之后,轻量级锁就会膨胀为重量级锁,重量级线程指针指向竞争线程,竞争线程也会阻塞,等待轻量级线程释放锁后唤醒他。

    Synchronized的可重入性

    • 重入锁: 当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁,请求将会成功
    • 实现: 一个线程得到一个对象锁后再次请求该对象锁,是允许的,每重入一次,monitor进入次数+1

    Synchronized与String锁

    • 隐患: 由于在JVM中具有String常量池缓存的功能,因此 相同字面量是同一个锁!!!
    • 注意: 严重不推荐将String作为锁对象,而应该改用其他非缓存对象
    • 提示: 对字面量有疑问的话请先回顾一下String的基础,这里不加以解释

    参考文章:

    https://blog.csdn.net/noble510520/article/details/78834224

    https://blog.csdn.net/noble510520/article/details/78834224

    http://ju.outofmemory.cn/entry/349840

  • 相关阅读:
    IntelliJ IDEA 2017 反向代理工具新方法激活
    如何去掉idea里mybatis的.xml文件 sql 语句背景色
    eclipse、idea切换大小写的快捷键
    pageHelper没有分页效果的问题
    springboot使用thymeleaf 解析异常
    SpringBoot发送简单文本邮件
    IDEA中使用git详细步骤
    解决 git branch -a 无法全部显示远程的分支,只显示master分支
    git常用命令
    异常处理
  • 原文地址:https://www.cnblogs.com/fruitknife/p/9647806.html
Copyright © 2011-2022 走看看