13张图,带大家深入理解Synchronized

2023-11-17

目录

 

前言

内容大纲

Synchronized使用方式

普通函数

静态函数

代码块

Synchronized原理

Synchronized优化

锁粗化

锁消除

锁升级

偏向锁

轻量级锁

重量级锁


前言

Java并发编程系列第二篇Synchronized,文章风格依然是图文并茂,通俗易懂,本文带读者们由浅入深理解Synchronized,让读者们也能与面试官疯狂对线。

在并发编程中Synchronized一直都是元老级的角色,Jdk 1.6以前大家都称呼它为重量级锁,相对于J U C包提供的Lock,它会显得笨重,不过随着Jdk 1.6Synchronized进行各种优化后,Synchronized性能已经非常快了。

内容大纲

图片

Synchronized使用方式

SynchronizedJava提供的同步关键字,在多线程场景下,对共享资源代码段进行读写操作(必须包含写操作,光读不会有线程安全问题,因为读操作天然具备线程安全特性),可能会出现线程安全问题,我们可以使用Synchronized锁定共享资源代码段,达到互斥mutualexclusion)效果,保证线程安全。

共享资源代码段又称为临界区critical section),保证临界区互斥,是指执行临界区critical section)的只能有一个线程执行,其他线程阻塞等待,达到排队效果。

图片

Synchronized的食用方式有三种

  • 修饰普通函数,监视器锁(monitor)便是对象实例(this

  • 修饰静态静态函数,视器锁(monitor)便是对象的Class实例(每个对象只有一个Class实例)

  • 修饰代码块,监视器锁(monitor)是指定对象实例

普通函数

普通函数使用Synchronized的方式很简单,在访问权限修饰符函数返回类型间加上Synchronized

图片

多线程场景下,threadthreadTwo两个线程执行incr函数,incr函数作为共享资源代码段被多线程读写操作,我们将它称为临界区,为了保证临界区互斥,使用Synchronized修饰incr函数即可。

public class SyncTest {

    private int j = 0;
    
    /**
     * 自增方法
     */
    public synchronized void incr(){
        //临界区代码--start
        for (int i = 0; i < 10000; i++) {
            j++;
        }
        //临界区代码--end
    }

    public int getJ() {
        return j;
    }
}

public class SyncMain {

    public static void main(String[] agrs) throws InterruptedException {
        SyncTest syncTest = new SyncTest();
        Thread thread = new Thread(() -> syncTest.incr());
        Thread threadTwo = new Thread(() -> syncTest.incr());
        thread.start();
        threadTwo.start();
        thread.join();
        threadTwo.join();
        //最终打印结果是20000,如果不使用synchronized修饰,就会导致线程安全问题,输出不确定结果
        System.out.println(syncTest.getJ());
    }

}

代码十分简单,incr函数被synchronized修饰,函数逻辑是对j进行10000次累加,两个线程执行incr函数,最后输出j结果。

synchronized修饰函数我们简称同步函数,线程执行称同步函数前,需要先获取监视器锁,简称锁,获取锁成功才能执行同步函数同步函数执行完后,线程会释放锁并通知唤醒其他线程获取锁,获取锁失败「则阻塞并等待通知唤醒该线程重新获取锁」,同步函数会以this作为锁,即当前对象,以上面的代码段为例就是syncTest对象。

图片

  • 线程thread执行syncTest.incr()

  • 线程thread获取锁成功

  • 线程threadTwo执行syncTest.incr()

  • 线程threadTwo获取锁失败

  • 线程threadTwo阻塞并等待唤醒

  • 线程thread执行完syncTest.incr()j累积到10000

  • 线程thread释放锁,通知唤醒threadTwo线程获取锁

  • 线程threadTwo获取锁成功

  • 线程threadTwo执行完syncTest.incr()j累积到20000

  • 线程threadTwo释放锁

静态函数

静态函数顾名思义,就是静态的函数,它使用Synchronized的方式与普通函数一致,唯一的区别是锁的对象不再是this,而是Class对象。

图片

多线程执行Synchronized修饰静态函数代码段如下。

public class SyncTest {

    private static int j = 0;
    
    /**
     * 自增方法
     */
    public static synchronized void incr(){
        //临界区代码--start
        for (int i = 0; i < 10000; i++) {
            j++;
        }
        //临界区代码--end
    }

    public static int getJ() {
        return j;
    }
}

public class SyncMain {

    public static void main(String[] agrs) throws InterruptedException {
        Thread thread = new Thread(() -> SyncTest.incr());
        Thread threadTwo = new Thread(() -> SyncTest.incr());
        thread.start();
        threadTwo.start();
        thread.join();
        threadTwo.join();
        //最终打印结果是20000,如果不使用synchronized修饰,就会导致线程安全问题,输出不确定结果
        System.out.println(SyncTest.getJ());
    }

}

Java的静态资源可以直接通过类名调用,静态资源不属于任何实例对象,它只属于Class对象,每个ClassJ V M中只有唯一的一个Class对象,所以同步静态函数会以Class对象作为锁,后续获取锁、释放锁流程都一致。

代码块

前面介绍的普通函数与静态函数粒度都比较大,以整个函数为范围锁定,现在想把范围缩小、灵活配置,就需要使用代码块了,使用{}符号定义范围给Synchronized修饰。

图片

下面代码中定义了syncDbData函数,syncDbData是一个伪同步数据的函数,耗时2秒,并且逻辑不涉及共享资源读写操作非临界区),另外还有两个函数incrincrTwo,都是在自增逻辑前执行了syncDbData函数,只是使用Synchronized的姿势不同,一个是修饰在函数上,另一个是修饰在代码块上。

public class SyncTest {

    private static int j = 0;


    /**
     * 同步库数据,比较耗时,代码资源不涉及共享资源读写操作。
     */
    public void syncDbData() {
        System.out.println("db数据开始同步------------");
        try {
            //同步时间需要2秒
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("db数据开始同步完成------------");
    }

    //自增方法
    public synchronized void incr() {
        //start--临界区代码
        //同步库数据
        syncDbData();
        for (int i = 0; i < 10000; i++) {
            j++;
        }
        //end--临界区代码
    }

    //自增方法
    public void incrTwo() {
        //同步库数据
        syncDbData();
        synchronized (this) {
            //start--临界区代码
            for (int i = 0; i < 10000; i++) {
                j++;
            }
            //end--临界区代码
        }

    }

    public int getJ() {
        return j;
    }

}


public class SyncMain {

    public static void main(String[] agrs) throws InterruptedException {
        //incr同步方法执行
        SyncTest syncTest = new SyncTest();
        Thread thread = new Thread(() -> syncTest.incr());
        Thread threadTwo = new Thread(() -> syncTest.incr());
        thread.start();
        threadTwo.start();
        thread.join();
        threadTwo.join();
        //最终打印结果是20000
        System.out.println(syncTest.getJ());

        //incrTwo同步块执行
        thread = new Thread(() -> syncTest.incrTwo());
        threadTwo = new Thread(() -> syncTest.incrTwo());
        thread.start();
        threadTwo.start();
        thread.join();
        threadTwo.join();
        //最终打印结果是40000
        System.out.println(syncTest.getJ());
    }

}

先看看incr同步方法执行,流程和前面没区别,只是Synchronized锁定的范围太大,把syncDbData()也纳入临界区中,多线程场景执行,会有性能上的浪费,因为syncDbData()完全可以让多线程并行并发执行。

图片

我们通过代码块的方式,来缩小范围,定义正确的临界区,提升性能,目光转到incrTwo同步块执行,incrTwo函数使用修饰代码块的方式同步,只对自增代码段进行锁定。

图片

代码块同步方式除了灵活控制范围外,还能做线程间的协同工作,因为Synchronized ()括号中能接收任何对象作为锁,所以可以通过Objectwait、notify、notifyAll等函数,做多线程间的通信协同(本文不对线程通信协同做展开,主角是Synchronized,而且也不推荐去用这些方法,因为LockSupport工具类会是更好的选择)。

  • wait:当前线程暂停,释放锁

  • notify:释放锁,唤醒调用了wait的线程(如果有多个随机唤醒一个)

  • notifyAll:释放锁,唤醒调用了wait的所有线程

Synchronized原理

  public class SyncTest {

    private static int j = 0;


    /**
     * 同步库数据,比较耗时,代码资源不涉及共享资源读写操作。
     */
    public void syncDbData() {
        System.out.println("db数据开始同步------------");
        try {
            //同步时间需要2秒
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("db数据开始同步完成------------");
    }

    //自增方法
    public synchronized void incr() {
        //start--临界区代码
        //同步库数据
        syncDbData();
        for (int i = 0; i < 10000; i++) {
            j++;
        }
        //end--临界区代码
    }

    //自增方法
    public void incrTwo() {
        //同步库数据
        syncDbData();
        synchronized (this) {
            //start--临界区代码
            for (int i = 0; i < 10000; i++) {
                j++;
            }
            //end--临界区代码
        }

    }

    public int getJ() {
        return j;
    }

} 

为了探究Synchronized原理,我们对上面的代码进行反编译,输出反编译后结果,看看底层是如何实现的(环境Java 11、win 10系统)。

  只截取了incr与incrTwo函数内容
        
  public synchronized void incr();
    Code:
       0: aload_0                                         
       1: invokevirtual #11                 // Method syncDbData:()V 
       4: iconst_0                          
       5: istore_1                          
       6: iload_1                                     
       7: sipush        10000               
      10: if_icmpge     27
      13: getstatic     #12                 // Field j:I
      16: iconst_1
      17: iadd
      18: putstatic     #12                 // Field j:I
      21: iinc          1, 1
      24: goto          6
      27: return

  public void incrTwo();    
    Code:
       0: aload_0
       1: invokevirtual #11                 // Method syncDbData:()V
       4: aload_0
       5: dup
       6: astore_1
       7: monitorenter                     //获取锁
       8: iconst_0
       9: istore_2
      10: iload_2
      11: sipush        10000
      14: if_icmpge     31
      17: getstatic     #12                 // Field j:I
      20: iconst_1
      21: iadd
      22: putstatic     #12                 // Field j:I
      25: iinc          2, 1
      28: goto          10
      31: aload_1
      32: monitorexit                      //正常退出释放锁 
      33: goto          41
      36: astore_3
      37: aload_1
      38: monitorexit                      //异步退出释放锁    
      39: aload_3
      40: athrow
      41: return

ps:对上面指令感兴趣的读者,可以百度或google一下“JVM 虚拟机字节码指令表”

先看incrTwo函数,incrTwo是代码块方式同步,在反编译后的结果中,我们发现存在monitorentermonitorexit指令(获取锁、释放锁)。

monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,J V M需要保证每一个 monitorenter都有monitorexit与之对应。

任何对象都有一个监视器锁(monitor)关联,线程执行monitorenter指令时尝试获取monitor的所有权。

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程为monitor的所有者

  • 如果线程已经占有该monitor,重新进入,则monitor的进入数加1

  • 线程执行monitorexitmonitor的进入数-1,执行过多少次monitorenter,最终要执行对应次数的monitorexit

  • 如果其他线程已经占用monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权

回过头看incr函数,incr是普通函数方式同步,虽然在反编译后的结果中没有看到monitorentermonitorexit指令,但是实际执行的流程与incrTwo函数一样,通过monitor来执行,只不过它是一种隐式的方式来实现,最后放一张流程图。

图片

Synchronized优化

Jdk 1.5以后对Synchronized关键字做了各种的优化,经过优化后Synchronized已经变得原来越快了,这也是为什么官方建议使用Synchronized的原因,具体的优化点如下。

  • 锁粗化

  • 锁消除

  • 锁升级

锁粗化

互斥的临界区范围应该尽可能小,这样做的目的是为了使同步的操作数量尽可能缩小,缩短阻塞时间,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁。

但是加锁解锁也需要消耗资源,如果存在一系列的连续加锁解锁操作,可能会导致不必要的性能损耗,锁粗化就是将「多个连续的加锁、解锁操作连接在一起」,扩展成一个范围更大的锁,避免频繁的加锁解锁操作。

图片

J V M会检测到一连串的操作都对同一个对象加锁(for循环10000次执行j++,没有锁粗化就要进行10000次加锁/解锁),此时J V M就会将加锁的范围粗化到这一连串操作的外部(比如for循环体外),使得这一连串操作只需要加一次锁即可。

锁消除

Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,经过逃逸分析(对象在函数中被使用,也可能被外部函数所引用,称为函数逃逸),去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的时间消耗。

图片

代码中使用Object作为锁,但是Object对象的生命周期只在incrFour()函数中,并不会被其他线程所访问到,所以在J I T编译阶段就会被优化掉(此处的Object属于没有逃逸的对象)。

锁升级

Java中每个对象都拥有对象头,对象头由Mark World 、指向类的指针、以及数组长度三部分组成,本文,我们只需要关心Mark World 即可,Mark World  记录了对象的HashCode、分代年龄和锁标志位信息。

Mark World简化结构

锁状态 存储内容 锁标记
无锁 对象的hashCode、对象分代年龄、是否是偏向锁(0) 01
偏向锁 偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1) 01
轻量级锁 指向栈中锁记录的指针 00
重量级锁 指向互斥量(重量级锁)的指针 10

读者们只需知道,锁的升级变化,体现在锁对象的对象头Mark World部分,也就是说Mark World的内容会随着锁升级而改变。

Java1.5以后为了减少获取锁和释放锁带来的性能消耗,引入了偏向锁轻量级锁Synchronized的升级顺序是 「无锁-->偏向锁-->轻量级锁-->重量级锁,只会升级不会降级

偏向锁

在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁,其目标就是在只有一个线程执行同步代码块时,降低获取锁带来的消耗,提高性能(可以通过J V M参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态)。

线程执行同步代码或方法前,线程只需要判断对象头的Mark Word中线程ID与当前线程ID是否一致,如果一致直接执行同步代码或方法,具体流程如下

图片

  • 无锁状态,存储内容「是否为偏向锁(0)」,锁标识位01

    • CAS设置当前线程ID到Mark Word存储内容中

    • 是否为偏向锁0 => 是否为偏向锁1

    • 执行同步代码或方法

  • 偏向锁状态,存储内容「是否为偏向锁(1)、线程ID」,锁标识位01

    • 对比线程ID是否一致,如果一致执行同步代码或方法,否则进入下面的流程

    • 如果不一致,CASMark Word的线程ID设置为当前线程ID,设置成功,执行同步代码或方法,否则进入下面的流程

    • CAS设置失败,证明存在多线程竞争情况,触发撤销偏向锁,当到达全局安全点,偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后在安全点的位置恢复继续往下执行。

轻量级锁

轻量级锁考虑的是竞争锁对象的线程不多,持有锁时间也不长的场景。因为阻塞线程需要C P U从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失,所以干脆不阻塞这个线程,让它自旋一段时间等待锁释放。

当前线程持有的锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。轻量级锁的获取主要有两种情况:① 当关闭偏向锁功能时;② 多个线程竞争偏向锁导致偏向锁升级为轻量级锁。

图片

  • 无锁状态,存储内容「是否为偏向锁(0)」,锁标识位01

    • 关闭偏向锁功能时

    • CAS设置当前线程栈中锁记录的指针到Mark Word存储内容

    • 锁标识位设置为00

    • 执行同步代码或方法

    • 释放锁时,还原来Mark Word内容

  • 轻量级锁状态,存储内容「线程栈中锁记录的指针」,锁标识位00(存储内容的线程是指"持有轻量级锁的线程")

    • CAS设置当前线程栈中锁记录的指针到Mark Word存储内容,设置成功获取轻量级锁,执行同步块代码或方法,否则执行下面的逻辑

    • 设置失败,证明多线程存在一定竞争,线程自旋上一步的操作,自旋一定次数后还是失败,轻量级锁升级为重量级锁

    • Mark Word存储内容替换成重量级锁指针,锁标记位10

重量级锁

轻量级锁膨胀之后,就升级为重量级锁,重量级锁是依赖操作系统的MutexLock互斥锁)来实现的,需要从用户态转到内核态,这个成本非常高,这就是为什么Java1.6之前Synchronized效率低的原因。

升级为重量级锁时,锁标志位的状态值变为10,此时Mark Word中存储内容的是重量级锁的指针,等待锁的线程都会进入阻塞状态,下面是简化版的锁升级过程。

图片

已获授权 转载自程序猿阿星

原文链接:https://mp.weixin.qq.com/s/esLXkYi3KiYMxYDiVXSnkA 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

13张图,带大家深入理解Synchronized 的相关文章

  • Semaphore 源码分析

    需要提前了解的知识点 AbstractQueuedSynchronizer 实现原理 类介绍 Semaphore 信号量 是用来控制同时访问特定资源的线程数量 它通过协调各个线程 以保证合理的使用公共资源 比如控制用户的访问量 同一时刻只允
  • 13张图,带大家深入理解Synchronized

    目录 前言 内容大纲 Synchronized使用方式 普通函数 静态函数 代码块 Synchronized原理 Synchronized优化 锁粗化 锁消除 锁升级 偏向锁 轻量级锁 重量级锁 前言 Java并发编程系列第二篇Synchr
  • Lock锁

    Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作 它们允许更灵活的结构化 可能具有完全不同的属性 并且可以支持多个相关联的对象Condition 1 传统的synchronized package cn d
  • 锁介绍名词解释&&Lock && synchronized

    各种锁名词解释及应用 一 名词解释 1 乐观锁 VS 悲观锁 2 自旋锁 VS 适应性自旋锁 3 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁 4 公平锁 VS 非公平锁 5 可重入锁 VS 非可重入锁 6 独享锁 VS 共享锁 二
  • brpc源码解析(十七)—— bthread上的类futex同步组件butex详解

    文章目录 一 futex简介 二 butex源码解析 2 1 butex相关数据结构 2 2 butex主要机制 2 2 1 butex wait 2 2 2 butex wake 我们知道在linux 下 锁和其他一些同步机制都会用到fu
  • CountDownLatch、CyclicBarrier、Semaphore源码解析

    1 CountDownLatch 计数器 CountDownLatch CountDownLatch 类位于java util concurrent包下 利用它可以实现类似计数器的功能 比如有一个任务A 它要等待其他4个任务执行完毕之后才能
  • java中的final变量和synchronized块

    Java中的final变量是什么 例如 如果我写final int temp 函数中final关键字的含义是什么 另外 我什么时候想使用final变量 既作为类变量又作为函数变量 为什么同步块中的变量必须声明为final Final 变量和
  • 在 Java 的 main() 中对 Thread 实例运行 wait()

    我正在尝试 java lang Object 中 wait 的定时版本 并观察到它在两种不同场景中的行为有所不同 场景1 使用Thread中run 的默认定义 public static void main String args thro
  • Java 同步方法...不同步

    对于我当前的 java 练习 我必须从 2 个不同的 Gmail 帐户获取邮件 我通过创建 Gmail 类的新实例来完成此操作 gmail 类扩展了线程 其中有一个同步方法 readMail 用于获取邮件并打印它 这个 readMail 方
  • 在函数中通过类名同步是否在扩展类中有效?

    我在基类中有一个方法 foo 使用 Synchronized 类名 以及扩展基类的两个类 A 和 B 如果我在两个不同的线程中从 A 实例和 B 实例调用 foo 它们会同步吗 这是一个示例代码 class BaseClass void f
  • 为什么同步字段变量并在同步块内递增它会导致打印乱序?

    我有一个简单的代码片段 public class ItemManager private Integer itemCount 0 public void incrementAndPrint synchronized this System
  • Java中的互斥方法执行

    我有两种方法 a and b 虽然我可以接受多个线程同时访问任何方法 这是可取的 但我不希望任何线程进入a while b 正在被执行 我怎么做 Edit 1 假设有 4 个线程Thread 1正在访问A 我想要的是所有 4 个线程都不应该
  • 如何同步对具有 didSet 的属性的访问?

    如何同步使用 didSet 的属性的访问 使用 GCD 或 objc sync enter 我有一处房产 有一个房产观察员 如何使用私有队列来同步属性的获取 设置 var state State disconnected Q How to
  • 两个同步方法是否同时执行

    我有4种方法 m1 m2 m3 and m4 在课堂上 方法m1 m2 and m3 are synchronized方法 另外 我有4个线程t1 t2 t3 and t4分别 If t1访问m1方法 同步方法 可以t2线程访问m2方法 同
  • Java同步块使用方法调用来获取同步对象

    我们正在编写一些锁定代码 并遇到了一个特殊的问题 我们使用 ConcurrentHashMap 来获取我们锁定的对象实例 所以我们的同步块看起来像这样 synchronized locks get key 我们重写了 ConcurrentH
  • 并发代码分析器

    我想知道是否有任何框架或应用程序 app 程序可以分析任何java代码的并发性 如果该工具知道 jre 提供的类和方法的所有实现 那么它就可以归结为对同步块和方法及其调用层次结构的简单分析 从这里它可以创建一个 Petri 网并确定地告诉您
  • 如何在 Julia 中使用锁

    我正在和朱莉娅一起工作 IDE 是 Juno 如果我是对的 async可以生成一个任务 它就像一个线程 所以我们可以这样做 async begin do something1 end async begin do something2 en
  • 如果一个同步方法调用另一个非同步方法,该非同步方法是否有锁

    在Java中 如果一个同步方法包含对非同步方法的调用 那么另一个方法是否仍然可以同时访问该非同步方法 基本上我要问的是同步方法中的所有内容都有锁 包括对其他同步方法的调用 如果一个同步方法调用另一个非同步方法 该非同步方法是否有锁 答案取决
  • BluetoothChat同步了Activity的onResume生命周期方法,为什么?

    我现在正在研究蓝牙 Android API 并且遇到了 BluetoothChat 示例 http developer android com resources samples BluetoothChat index html http
  • 同步与 ReadWriteLock 性能

    我试图证明当有很多读者而只有一些作者时同步会更慢 不知怎的 我证明了相反的情况 以 RW 为例 执行时间为 313 ms package zad3readWriteLockPerformance import java util Array

随机推荐

  • PAT甲级刷题:模拟(不断更新)

    目录 1001 A B Format 1005 Spell It Right 1035 Password 1061 Dating 18 20 1073 Scientific Notation 16 20 1077 Kuchiguse 17
  • Java 华为真题-猴子爬山

    需求 一天一只顽猴想去从山脚爬到山顶 途中经过一个有个N个台阶的阶梯 但是这猴子有一个习惯 每一次只能跳1步或跳3步 试问猴子通过这个阶梯有多少种不同的跳跃方式 输入描述 输入只有一个整数N 0
  • k8s中各组件和kube apiserver通信时的认证和鉴权

    背景 和master节点kube api server通信的组件有很多 包括 kubelet calico scheduler kubectl 某些pod可能会和kube api server通信 这些组件和api server通信时用的是
  • Springboot使用EasyExcel读写excel(详细)

    文章目录 使用EasyExcel读取Excel 一 关于EasyExcel 二 读取excel 实体类 本地读取 controller上传 创建监听器 三 导出excel 实体类 本地导出 Controller下载 使用EasyExcel读
  • 模糊pid控制的温度系统matlab源代码_模糊PID控制系统(一)模糊入门

    1 matlab模糊工具箱 1 1 matlab命令 fuzzy 打开fuzzy设计工具箱 1 2 添加输入输出 隶属度函数 数值范围 1 3 确定模糊规则表 添加模糊规则 也可在matlab 编辑器里编辑 fis文件 1 4 反模糊化 1
  • HTTP协议之multipart/form-data请求分析

    无意中发现了一个巨牛的人工智能教程 忍不住分享一下给大家 教程不仅是零基础 通俗易懂 而且非常风趣幽默 像看小说一样 觉得太牛了 所以分享给大家 点这里可以跳转到教程 首先来了解什么是multipart form data请求 根据http
  • hook-setInterval定时器

    目标网站 aHR0cDovL3NwaWRlci53YW5nbHVvemhlLmNvbS9jaGFsbGVuZ2UvNQ 背景 本题为hook初体验 目标是用hook的方式过掉定时器 成功在控制台中打印出我们想要的内容 表现形式 1 一进入页
  • 【Java愚公】Windows安装wsl2

    Windows安装wsl2 查看window有没有安装wsl 在window下安装wsl 查看window有没有安装wsl 在cmd命令面板输入 wsl 没有安装wsl输入会报错 在window下安装wsl 通过在管理员PowerShell
  • js实现图片上下滚动background-position

  • 关于js报错 Cannot read property innerHTML of null和关于Cannot set property ‘innerHTML’ of null 错误原因

    解决关于js报错 Cannot read property innerHTML of null 1 相信很多同学在开发过程中都会遇到 Cannot read property innerHTML of null 这个报错的字面含义是 不能读
  • 前端文件下载的八种方法(解决pdf、图片在浏览器自动打开问题)

    系列文章目录 现在流行的chrom 和火狐浏览器 都会将图片和文档自动打开 图片自动打开的问题已经解决 请看第三条 提示 下面代码中会用到a标签中 target 会添加一个 view window 的属性 如果想要详细了解a标签的属性 可以
  • rabbitMQ初识

    消息队列 RabbitMQ 认识MQ 同步和异步通讯 微服务间通讯有同步和异步两种方式 同步通讯 就像打电话 需要实时响应 异步通讯 就像发邮件 不需要马上回复 同步通讯 同步调用的优点 时效性较强 可以立即得到结果 同步调用的问题 耦合度
  • C++之多重继承

    大多数应用程序使用单个基类的公用继承 但是在某些情况下 单继承是不够的 必须使用多继承 C 允许为一个派生类指定多个基类 这样的继承结构被称做多重继承 举个例子 交通工具类可以派生出汽车和船连个子类 但拥有汽车和船共同特性水陆两用汽车就必须
  • 使用Android studio开发第一个小程序

    1 点击新建安卓项目 填入项目名称 公司域 项目的修饰 项目路径 若不存在 会新建一个路径 下面两个不要选 点击下一步 2 接下来就是项目配置了 在这里我们只勾选第一个 适配的手机系统最小sdk版本 目前经常用的是API 17 当然你也可以
  • 深入理解浏览器缓存机制 ( http )

    一 介绍 http缓存 浏览器根据当前http请求报文策略 将网路资源存储到本地内存 memory cache 硬盘 disk cache 中 缓存流程 浏览器 浏览器缓存 服务端 发起请求 根据缓存
  • dns服务器经赏要修复,十要诀帮你修复DNS域名解析服务故障

    十个要诀帮你修复DNS故障 1 DNS是网络基础协议之一 想必大家都应该有所了解 对于所有基于Windows系统的网络来说 DNS都属于最重要的服务之一 在没有DNS支持的情况下 活动目录就不能正常工作 并且它使用到的功能也比任何其它类型的
  • 【C++】赋值运算符重载的返回值分析

    转载 https blog csdn net Always article details 50532323 其实对于重载赋值运算符 返回值是引用或者不是都行 代码都可以运行 之所以用引用是为了提高代码效率 为什么引用就会提高代码效率呢 对
  • 使用docker-maven-plugin插件构建镜像并推送至私服Harbor

    前言 如下所示 建议使用 Dockerfile Maven 插件 但该插件也停止维护更新了 因此先暂时使用docker maven plugin插件 一 开启Docker服务器的远程访问 1 1 开启2375远程访问 默认的dokcer是不
  • b+树和b树的区别

    B 树和B 树是两种在数据库索引实现中经常使用的平衡树 在实际应用中被广泛采用 B 树和B 树都是基于平衡树的数据结构 用来实现数据的索引和查找 它们都支持对数据的插入 删除和查找等操作 并且可以在较短的时间内完成数据的查找和遍历等操作 但
  • 13张图,带大家深入理解Synchronized

    目录 前言 内容大纲 Synchronized使用方式 普通函数 静态函数 代码块 Synchronized原理 Synchronized优化 锁粗化 锁消除 锁升级 偏向锁 轻量级锁 重量级锁 前言 Java并发编程系列第二篇Synchr