• synchronized原理


    在多线程中同时进行i++操作 不能保证i的原子性。i++ 可以看作为为以下几个步骤

    1.读取i的值

    2.计算i+1

    3.赋值

    在多线程下 可能还在没有来得及赋值 其他线程已经复制,再赋值就是脏数据

    synchronized则能保证原子性。synchronized 一个线程获得锁对象则会将对象标记为锁定状态。执行完毕之后释放锁

    synchronize的三个特性使用方式

    原子性

    如i++  分为读取 计算  复制, 在这3步没有执行完毕之前 其他线程不能执行

    可见性

    在原子性的前提下,因为释放锁会将最新值刷入主内存。保证后面获取所得线程获取到的是最新的值

    有序性

    Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性

    可重入性

    synchronized和ReentrantLock都是可重入锁。当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。

    synchronize的三种使用方式

    • 修饰实例方法
    • 修饰静态方法
    • 修饰代码块

    修饰实例方法

    .... 
     public synchronized   void  account(){
            i++;
        }
    ....

    修饰静态方法

    ... 
    public static synchronized   void  account(){
            i++;
        }
    ...

    修饰代码块

    使用this

     public   void  account(){
            synchronized (this) {
                 i++;
            }
        }

    使用对象

    int i=0;
        Object lockObj=new Object();
        public   void  account(){
            synchronized (lockObj) {
                 i++;
            }
        }

    使用class

     public   void  account(){
            synchronized (Accounting.class) {
                 i++;
            }
        }

    sychronized原理

    反编译

     * @author liqiang
     * @date 2020/3/30 15:52
     * @Description: (what)
     * (why)
     * (how)
     */
    public class Accounting implements Runnable {
        int i=0;
        public  void  account(){
            synchronized (this) {
    
                i++;
            }
        }
        @Override
        public void run() {
            for (int j=0;j<2000;j++){
                account();
    
            }
        }
        public int getI() {
            return i;
        }
        public static void main(String[] args) throws InterruptedException {
            Accounting accounting= new Accounting();
            Thread t=new Thread(accounting,"a1");
            Thread t2=new Thread(accounting,"a2");
            t.start();
            t2.start();
            t.join();//主线程挂起等待这个线程执行完毕在网下执行
            t2.join();
            System.out.print(accounting.getI());
        }
    }

    1.编译class文件

    javac -encoding UTF-8 Accouting.java //先运行编译class文件命令

    2.打印

    javap -v Accouting.class //再通过javap打印出字节文件
     public void account();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=3, args_size=1
             0: aload_0
             1: dup
             2: astore_1
             3: monitorenter //monintorenter指令
             4: aload_0
             5: dup
             6: getfield      #2                  // Field i:I
             9: iconst_1
            10: iadd
            11: putfield      #2                  // Field i:I
            14: aload_1
            15: monitorexit //monitorexit指令
            16: goto          24
            19: astore_2
            20: aload_1
            21: monitorexit
            22: aload_2
            23: athrow
            24: return
          Exception table:
             from    to  target type
                 4    16    19   any
                19    22    19   any
          LineNumb 

    在进入monitorenter指令后 线程将持有Monitor,在进入monnitorexit指令后将释放Monitor对象

    monitor的实现类是ObjectMonitor

    主要成员包括_WaitSet  _EntryList  _Owner 用来保存ObjectWaiter对象(每个等待锁的线程都会封装成ObjectWaiter)

    当多线程同时访问一段同步代码块会首先进入_EntryList状态为block,获得锁的线程则会设置到_oWner 同时monitor对象的count+1

    如果调用线程的wait方法则清空_oWner count-1 同时当前线程进入_WaitSet等待被唤醒

    如果当前线程执行完毕也将清空_Owner count-1

    其他block线程再次发起竞争

    monitor结构

    ObjectMonitor() {
       _header = NULL;
       _count = 0; //记录个数
       _waiters = 0,
       _recursions = 0;
       _object = NULL;
       _owner = NULL;
       _WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet
       _WaitSetLock = 0 ;
       _Responsible = NULL ;
       _succ = NULL ;
       _cxq = NULL ;
       FreeNext = NULL ;
       _EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表
       _SpinFreq = 0 ;
       _SpinClock = 0 ;
       OwnerIsThread = 0 ;
    } 

    线程中断

    public void interrupt();//只能中断阻塞线程 非阻塞线程只会给当前线程增加中断标志,线程还是会继续往下执行
    public  boolean isInterrupted(); //判断线程是否中断
    public static boolean interrupted();//判断线程是否中断并且会清除中断状态
    package com.liqiang.sychronize;
    
    public class Accounting implements Runnable {
        int i = 0;
    
        public void account() {
    
        }
    
        @Override
        public void run() {
            try {
                while (true) {
    
                    Thread.sleep(2000);
    
                    System.out.println("1");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public int getI() {
            return i;
        }
    
        public static void main(String[] args) throws InterruptedException {
            Accounting accounting = new Accounting();
            Thread t = new Thread(accounting, "a1");
            t.start();
            Thread.sleep(4000);
            t.interrupt();
            System.out.println(t.isInterrupted());
            t.join();//主线程挂起等待这个线程执行完毕在往下执行
    
        }
    }
    interrupt方法只能中断阻塞线程(需要try捕获异常 否则会一直执行下去)非阻塞线程需要我们手动中断
    package com.liqiang.sychronize;
    
    public class Accounting implements Runnable {
        int i = 0;
    
        public void account() {
    
        }
    
        @Override
        public void run() {
    
                while (true) {
                        if(Thread.currentThread().isInterrupted()){
                            break;
                        }
                    System.out.println("1");
                }
    
        }
    
        public int getI() {
            return i;
        }
    
        public static void main(String[] args) throws InterruptedException {
            Accounting accounting = new Accounting();
            Thread t = new Thread(accounting, "a1");
            t.start();
            Thread.sleep(4000);
            t.interrupt();
            System.out.println(t.isInterrupted());
            t.join();//主线程挂起等待这个线程执行完毕在网下执行
    
        }
    }

    sleep和wait的区别

    Thread.sleep 与wait的区别  wait会将线程锁释放 线程保存到monitor的 _waitSet里面 等待被唤醒,当调用notify() 唤醒线程,唤醒线程并不是立即执行,而是重新获取锁(注:调用wait必须已经持有锁), sleep是线程休眠并不释放锁

    jdk1.6的锁优化

    在 JDK1.6 JVM 中,对象实例在堆内存中被分为了三个部分:对象头、实例数据和对齐填充。其中 Java 对象头由 Mark Word、指向类的指针以及数组长度三部分组成。

    Mark Word记录了对象和锁的相关信息

      锁升级主要由Mark Word 中的锁标志位和释放偏向锁标志位,Synchronized 同步锁就是从偏向锁开始的,随着竞争越来越激烈,偏向锁升级到轻量级锁,最终升级到重量级锁。

    偏向锁

    偏向锁主要用来优化同一线程多次申请同一个锁的竞争。在某些情况下,大部分时间是同一个线程竞争锁资源

    线程执行同步代码块的时候 只需要到Mark Word 中去判断一下是否有偏向锁指向它的 ID,无需再进入 Monitor 去竞争对象了。当对象被当做同步锁并有一个线程抢到了锁时,锁标志位还是 01,“是否偏向锁”标志位设置为 1,并且记录抢到锁的线程 ID,表示进入偏向锁状态。

    一旦出现其它线程竞争锁资源时,偏向锁就会被撤销。偏向锁的撤销需要等待全局安全点,暂停持有该锁的线程,同时检查该线程是否还在执行该方法,如果是,则升级锁,反之则被其它线程抢占

    偏向锁生成的流程:摘自《极客时间-java调优实战》

    public class Accounting implements Runnable {
        int i=0;
        public  void  account(){
            synchronized (this) {
                i++;
            }
        }
        @Override
        public void run() {
            for (int j=0;j<2000;j++){
                account();
    
            }
        }
        public int getI() {
            return i;
        }
        public static void main(String[] args) throws InterruptedException {
            Accounting accounting= new Accounting();
            Thread t=new Thread(accounting,"a1");
            t.start();
            t.join();//主线程挂起等待这个线程执行完毕在网下执行
            Thread t2=new Thread(accounting,"a2");
            t2.start();
            t2.join();
            System.out.print(accounting.getI());
        }
    }

    上面这种情况虽然有2个线程 但是不存在锁竞争所以偏向锁 就发生了优化的作用。但是,在高并发场景下,当大量线程同时竞争同一个锁资源时,偏向锁就会被撤销,发生 stop the word 后, 开启偏向锁无疑会带来更大的性能开销,这时我们可以通过添加 JVM 参数关闭偏向锁来调优系统性能,

    -XX:-UseBiasedLocking //关闭偏向锁(默认打开)
    或者
    -XX:+UseHeavyMonitors  //设置重量级锁

    轻量级锁

    当有另外一个线程竞争获取这个锁时,由于该锁已经是偏向锁,当发现对象头 Mark Word 中的线程 ID 不是自己的线程 ID,就会进行 CAS 操作获取锁,如果获取成功,直接替换 Mark Word 中的线程 ID 为自己的 ID,该锁会保持偏向锁状态;如果获取锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。轻量级锁适用于线程交替执行同步块的场景,绝大部分的锁在整个同步周期内都不存在长时间的竞争。

    public class Accounting implements Runnable {
        int i=0;
        public  void  account(){
            synchronized (this) {
                i++;
            }
        }
        @Override
        public void run() {
            for (int j=0;j<2000;j++){
                account();
    
            }
        }
        public int getI() {
            return i;
        }
        public static void main(String[] args) throws InterruptedException {
            Accounting accounting= new Accounting();
            Thread t=new Thread(accounting,"a1");
            t.start();
            Thread t2=new Thread(accounting,"a2");
            t2.start();
            t2.join();
            t.join();//主线程挂起等待这个线程执行完毕在网下执行
            System.out.print(accounting.getI());
        }
    }

    虽然a1和a2存在竞争,但是当a1执行期间 a2 cas自旋重试获取锁(固定重试次数 如果重试失败则暂停所有线程 开始升级锁)。a1不是耗时操作0.几毫秒释放后a2继续执行

    所以针对存在竞争但是竞争不大的情况下轻量级锁才能发挥做用

    偏向锁升级为轻量级锁图 摘自《极客时间java性能调优实战》

    自旋锁与重量级锁

    轻量级锁 CAS 抢锁失败,线程将会被挂起进入阻塞状态。如果正在持有锁的线程在很短的时间内释放资源,那么进入阻塞状态的线程无疑又要申请锁资源。JVM 提供了一种自旋锁,可以通过自旋方式不断尝试获取锁,从而避免线程被挂起阻塞。这是基于大多数情况下,线程持有锁的时间都不会太长,毕竟线程被挂起阻塞可能会得不偿失。从 JDK1.7 开始,自旋锁默认启用,自旋次数由 JVM 设置决定,这里我不建议设置的重试次数过多,因为 CAS 重试操作意味着长时间地占用 CPU。自旋锁重试之后如果抢锁依然失败,同步锁就会升级至重量级锁,锁标志位改为 10。在这个状态下,未抢到锁的线程都会进入 Monitor,之后会被阻塞在 _WaitSet 队列中。、

    重量级锁图,摘自:《极客时间-java调优实战

    锁粗化

    就是在 JIT 编译器动态编译时,如果发现几个相邻的同步块使用的是同一个锁实例,那么 JIT 编译器将会把这几个同步块合并为一个大的同步块,从而避免一个线程“反复申请、释放同一个锁“所带来的性能开销。

    优化前

        public  void  account(){
            ArrayList<Integer> arrs=new ArrayList<>();
            for(int i=0;i<10;i++){
                synchronized (Accounting.class){
                    arrs.add(i);
                }
            }
        }

    优化后

      public void account() {
            synchronized (Accounting.class) {
                ArrayList<Integer> arrs = new ArrayList<>();
                for (int i = 0; i < 10; i++) {
                    arrs.add(i);
    
                }
            }
        }

    锁消除

     public void account() {
            StringBuilder stringBuilder=new StringBuilder();
            stringBuilder.append("1");
        }

    个人理解 虽然StringBuilder的append是同步方法,但是StringBuilder是方法内变量 不存在锁竞争 就会吧同步锁去掉(偏向锁) 

  • 相关阅读:
    jQuery基础
    Jquery正则表达式公式.例子
    jquery对象与js对象的相互转换
    windows用命令结束进程
    禅道 bug指向为数字问题解决过程
    delphi 触摸 手势
    二维码
    PowerDesigner 生成的脚本取掉双引号
    oracle执行sql文件
    fireDAC oracle
  • 原文地址:https://www.cnblogs.com/LQBlog/p/8698382.html
Copyright © 2020-2023  润新知