<并发编程>学习笔记------(一) 并发相关理论

2023-11-08

前面

并发编程可以总结为三个核心问题:

  • 分工指的是如何高效地拆解任务并分配给线程
  • 同步指的是线程之间如何协作
  • 互斥则是保证同一时刻只允许一个线程访问共享资源

并发相关理论

可见性、原子性和有序性

核心矛盾
CPU、内存、I/O 设备的速度差异
cpu >>> 内存 >>> I/O 设备

CPU 增加了缓存,以均衡与内存的速度差异
操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异
编译程序优化指令执行次序,使得缓存能够得到更加合理地利用

缓存导致的可见性问题

可见性: 一个线程对共享变量的修改,另外一个线程能够立刻看到

在这里插入图片描述
多核 CPU 的缓存与内存关系图

多核时代,每颗 CPU 都有自己的缓存
当多个线程在不同的 CPU 上执行时,这些线程操作的是不同的 CPU 缓存
这个时候线程 A 对变量 V 的操作对于线程 B 而言就不具备可见性了

线程切换带来的原子性问题

在这里插入图片描述
线程切换示意图

多进程: 单核的 CPU可以同时执行多个任务
时间片: 某个进程执行的一小段时间, 之后进行任务切换
一个时间片内, 某个进程IO可以先休眠, 让出CPU使用权, 读入内存后再由OS唤醒该进程, 可以同时提高CPU和IO的使用率
一个进程创建的所有线程共享一个内存空间的,不需要切换内存映射地址

count += 1, 三条CPU指令:
指令 1:首先,需要把变量 count 从内存加载到 CPU 的寄存器;
指令 2:之后,在寄存器中执行 +1 操作;
指令 3:最后,将结果写入内存(缓存机制导致可能写入的是 CPU 缓存而不是内存)。

操作系统做任务切换,可以发生在任何一条 CPU 指令执行完

在这里插入图片描述

假设 count=0,如果线程 A 在指令 1 执行完后做线程切换,线程 A 和线程 B 按照下图的序列执行,那么我们会发现两个线程都执行了 count+=1 的操作,但是得到的结果不是我们期望的 2,而是 1

原子性: 一个或者多个操作在 CPU 执行的过程中不被中断的特性

编译优化带来的有序性问题
应该这样写
class Single{  
    private static volatile Single s = null;   //禁止重排序
    private Single(){}  

    public static Single getInstance(){
        if(null==s){
            synchronized(Single.class){
                if(null==s)  
                    s = new Single();  
            }
        }
        return s;  
    }  
}
双重检查创建单例对象
public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}

假设有两个线程 A、B 同时调用 getInstance() 方法,他们会同时发现 instance == null ,于是同时对 Singleton.class 加锁,此时 JVM 保证只有一个线程能够加锁成功(假设是线程 A),另外一个线程则会处于等待状态(假设是线程 B);

线程 A 会创建一个 Singleton 实例,之后释放锁,锁释放后,线程 B 被唤醒,线程 B 再次尝试加锁,此时是可以加锁成功的,加锁成功后,线程 B 检查 instance == null 时会发现,已经创建过 Singleton 实例了,所以线程 B 不会再创建一个 Singleton 实例

getInstance 还是存在问题 – 重排序 – volatile!!!

new 操作:

  1. 分配一块内存 M;
  2. 在内存 M 上初始化 Singleton 对象;
  3. 然后 M 的地址赋值给 instance 变量。

优化后的执行顺序:

  1. 分配一块内存 M;
  2. 将 M 的地址赋值给 instance 变量;
  3. 最后在内存 M 上初始化 Singleton 对象。

产生问题: 空指针异常
假设线程 A 先执行 getInstance() 方法,当执行完指令 2 时恰好发生了线程切换,切换到了线程 B 上;如果此时线程 B 也执行 getInstance() 方法,那么线程 B 在执行第一个判断时会发现 instance != null ,所以直接返回 instance,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常
在这里插入图片描述
双重检查创建单例的异常执行路径

思考

在 32 位的机器上对 long 型变量进行加减操作存在并发隐患
long类型64位,所以在32位的机器上,对long类型的数据操作通常需要多条指令组合出来,无法保证原子性,所以并发的时候会出问题

java内存模型, 按需禁用缓存以及编译优化

通过volatile, synchronized, final关键字和happens-before规则

volatile

禁用 CPU 缓存
volatile int x = 0:告诉编译器,对这个变量的读写,不能使用 CPU 缓存,必须从内存中读取或者写入

Happens-Before 规则

Happens-Before 规则: 前面一个操作的结果对后续操作是可见的, 约束了编译器的优化行为

  1. 程序的顺序性规则
    前面的操作 Happens-Before 于后续的任意操作
  2. volatile 变量规则
    对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作
  3. 传递性
    如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C
  4. 管程synchronized中锁的规则
    对一个锁的解锁 Happens-Before 于后续对这个锁的加锁
    前一个线程的解锁操作对后一个线程的加锁操作可见
    管程是一种通用的同步原语,在 Java 中指的就是 synchronized,synchronized 是 Java 里对管程的实现
    管程中的锁在 Java 里是隐式实现的,例如下面的代码,在进入同步块之前,会自动加锁,而在代码块执行完会自动释放锁,加锁以及释放锁都是编译器帮我们实现的
synchronized (this) { //此处自动加锁
  // x是共享变量,初始值=10
  if (this.x < 12) {
    this.x = 12; 
  }  
} //此处自动解锁
  1. 线程 start() 规则
    主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B 的 join() 方法实现)
    当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作。
    当然所谓的“看到”,指的是对共享变量的操作
    eg: 如果线程 A 调用线程 B 的 start() 方法(即在线程 A 中启动线程 B),那么该 start() 操作 Happens-Before 于线程 B 中的任意操作
  2. 线程 join() 规则
    如果在线程 A 中,调用线程 B 的 join() 并成功返回,那么线程 B 中的任意操作 Happens-Before 于该 join() 操作的返回
Thread B = new Thread(()->{
  // 此处对共享变量var修改
  var = 66;
});

// 例如此处对共享变量修改,则这个修改结果对线程B可见
// 主线程启动子线程
B.start();
B.join()
// 子线程所有对共享变量的修改, 在主线程调用B.join()之后皆可见
// 此例中,var==66
final

volatile 为的是禁用缓存以及编译优化
final 关键字: 这个变量生而不变

利用双重检查方法创建单例,构造函数的错误重排导致线程可能看到 final 变量的值会变化

final int x;
// 错误的构造函数
// 在构造函数里面将 this 赋值给了全局变量 global.obj,这就是“逸出”,线程通过 global.obj 读取 x 是有可能读到 0 的
public FinalFieldExample() { 
  x = 3;
  y = 4;
  // 此处就是讲this逸出,
  global.obj = this;
}
思考

有一个共享变量 abc,在一个线程里设置了 abc 的值 abc=3,你思考一下,有哪些办法可以让其他线程能够看到abc==3?

1.声明共享变量abc,并使用volatile关键字修饰abc
2.声明共享变量abc,在synchronized关键字对abc的赋值代码块加锁,由于Happen-before管程锁的规则,可以使得后续的线程可以看到abc的值。
3.A线程启动后,使用A.JOIN()方法来完成运行,后续线程再启动,则一定可以看到abc==3

总结
  1. 为什么定义Java内存模型?现代计算机体系大部是采用的对称多处理器的体系架构。每个处理器均有独立的寄存器组和缓存,多个处理器可同时执行同一进程中的不同线程,这里称为处理器的乱序执行。在Java中,不同的线程可能访问同一个共享或共享变量。如果任由编译器或处理器对这些访问进行优化的话,很有可能出现无法想象的问题,这里称为编译器的重排序。除了处理器的乱序执行、编译器的重排序,还有内存系统的重排序。因此Java语言规范引入了Java内存模型,通过定义多项规则对编译器和处理器进行限制,主要是针对可见性和有序性。
  2. 三个基本原则:原子性、可见性、有序性。
  3. Java内存模型涉及的几个关键词:锁、volatile字段、final修饰符与对象的安全发布。其中:第一是锁,锁操作是具备happens-before关系的,解锁操作happens-before之后对同一把锁的加锁操作。实际上,在解锁的时候,JVM需要强制刷新缓存,使得当前线程所修改的内存对其他线程可见。第二是volatile字段,volatile字段可以看成是一种不保证原子性的同步但保证可见性的特性,其性能往往是优于锁操作的。但是,频繁地访问 volatile字段也会出现因为不断地强制刷新缓存而影响程序的性能的问题。第三是final修饰符,final修饰的实例字段则是涉及到新建对象的发布问题。当一个对象包含final修饰的实例字段时,其他线程能够看到已经初始化的final实例字段,这是安全的。
  4. Happens-Before的7个规则:
    (1).程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
    (2).管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而"后面"是指时间上的先后顺序。
    (3).volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的"后面"同样是指时间上的先后顺序。
    (4).线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
    (5).线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
    (6).线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
    (7).对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
  5. Happens-Before的1个特性:传递性。
  6. Java内存模型底层怎么实现的?主要是通过内存屏障(memory barrier)禁止重排序的,即时编译器根据具体的底层体系架构,将这些内存屏障替换成具体的 CPU 指令。对于编译器而言,内存屏障将限制它所能做的重排序优化。而对于处理器而言,内存屏障将会导致缓存的刷新操作。比如,对于volatile,编译器将在volatile字段的读写操作前后各插入一些内存屏障。

互斥锁, 解决原子性问题

原子性问题: 线程切换
操作系统做线程切换是依赖 CPU 中断的,所以禁止 CPU 发生中断就能够禁止线程切换

eg: 32 位 CPU 上执行 long 型变量的写操作
明明已经把变量成功写入内存,重新读出来却不是自己写入的
在这里插入图片描述

在单核 CPU 场景下,同一时刻只有一个线程执行,禁止 CPU 中断,意味着操作系统不会重新调度线程,也就是禁止了线程切换,获得 CPU 使用权的线程就可以不间断地执行,所以两次写操作一定是:要么都被执行,要么都没有被执行,具有原子性

但是在多核场景下,同一时刻,有可能有两个线程同时在执行,一个线程执行在 CPU-1 上,一个线程执行在 CPU-2 上,此时禁止 CPU 中断,只能保证 CPU 上的线程连续执行,并不能保证同一时刻只有一个线程执行,如果这两个线程同时写 long 型变量高 32 位的话,那就有可能出现诡异 Bug : 明明已经把变量成功写入内存,重新读出来却不是自己写入的

同一时刻只有一个线程执行
保证对共享变量的修改是互斥

简易锁模型

在这里插入图片描述
临界区: 一段需要互斥执行的代码

改进后的锁模型

在这里插入图片描述

受保护的资源 R
要保护资源 R 就得为它创建一把锁 LR
针对这把锁 LR,在进出临界区时添上加锁操作和解锁操作
锁 LR 和受保护资源之间,保护自家的资源!!

Java 语言提供的锁技术:synchronized 管程
class X {
  // 修饰非静态方法
  synchronized void foo() {
    // 临界区
  }
  
  // 修饰静态方法
  synchronized static void bar() {
    // 临界区
  }
  
  // 修饰代码块
  Object obj = new Object()void baz() {
    synchronized(obj) {
      // 临界区
    }
  }
}  

Java 编译器会在 synchronized 修饰的方法或代码块前后自动加上加锁 lock() 和解锁 unlock(),这样做的好处就是加锁 lock() 和解锁 unlock() 一定是成对出现的,毕竟忘记解锁 unlock() 可是个致命的 Bug(意味着其他线程只能死等下去了)

当修饰静态方法的时候,锁定的是当前类的 Class 对象,在上面的例子中就是 Class X;
当修饰非静态方法的时候,锁定的是当前实例对象 this

synchronized 修饰静态方法相当于:

class X {
  // 修饰静态方法
  synchronized(X.class) static void bar() {
    // 临界区
  }
}

修饰非静态方法,相当于:

class X {
  // 修饰非静态方法
  synchronized(this) void foo() {
    // 临界区
  }
}
用 synchronized 解决 count+=1 问题
class SafeCalc {
  long value = 0L;
  long get() {
    return value;
  }
  synchronized void addOne() {
    value += 1;
  }
}

addOne方法:
  1. 原子性
  addOne() 方法,被 synchronized 修饰后,无论是单核 CPU 还是多核 CPU,只有一个线程能够执行 addOne() 方法,
  所以一定能保证原子操作
  2. 可见性
  2.1 synchronized 修饰的临界区是互斥的,也就是说同一时刻只有一个线程执行临界区的代码
  2.2 管程(synchronized)中锁的规则:对一个锁的解锁 Happens-Before 于后续对这个锁的加锁
  	  前一个线程的解锁操作对后一个线程的加锁操作可见
  2.3 综合 Happens-Before 的传递性原则
      前一个线程在临界区修改的共享变量(该操作在解锁之前),对后续进入临界区(该操作在加锁之后)的线程是可见的

问题: 执行 addOne() 方法后,value 的值对 get() 方法是可见的吗?这个可见性是没法保证的
get() 方法并没有加锁操作,所以可见性没法保证

问题的解决: get() 方法也 synchronized 一下

class SafeCalc {
  long value = 0L;
  synchronized long get() {
    return value;
  }
  synchronized void addOne() {
    value += 1;
  }
}

在这里插入图片描述
锁模型示意图

保护临界区 get() 和 addOne() 的示意图

锁和受保护资源的关系

合理的关系应该是: 受保护资源和锁之间的关联关系是 N:1 的关系
使用一把

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

<并发编程>学习笔记------(一) 并发相关理论 的相关文章

  • Java并发编程实战——并发容器之ThreadLocal及其内存泄漏问题

    文章目录 ThreadLocal的简介 ThreadLocal的实现原理 ThreadLocalMap详解 ThreadLocal内存泄漏问题 ThreadLocal的使用场景 ThreadLocal的简介 之前写过用ThreadLocal
  • 并发编程系列之Fork/Join

    前言 上节我们讲了阻塞队列 Java中的并发容器就算有了个基本的认识 今天我们来介绍一种线程工作模式 叫Fork Join 他是JDK7之后提供的一个并行执行框架 主要的思想我觉得是分而治之 将一个大的任务分成多个小的任务并行执行 然后等所
  • Java 线程池ExecutorService 等待队列问题

    本人博客原地址 Java 线程池ExecutorService 等待队列问题 创作时间 2019 09 30 11 12 35 1 首先看下Executor获取线程池 这样方式 可以设置线程池的大小 但是了解线程池的内部原理的情况下 这样的
  • 浅析多线程中的各种锁

    高并发的场景下 如果选对了合适的锁 则会大大提高系统的性能 否则性能会降低 所以 知道各种锁的开销 以及应用场景是很有必要的 文章目录 常用的各种锁 互斥锁与自旋锁 互斥锁 自旋锁 读写锁 乐观锁与悲观锁 本文小结 常用的各种锁 多线程访问
  • java.util.concurrent.locks.ReentrantReadWriteLock 读写锁

    读写锁简介 对共享资源有读和写的操作 且写操作没有读操作那么频繁 在没有写操作的时候 多个线程同时读一个资源没有任何问题 所以应该允许多个线程同时读取共享资源 但是如果一个线程想去写这些共享资源 就不应该允许其他线程对该资源进行读和写的操作
  • 单例模式的4种写法

    单例模式是开发过程中常用的模式之一 首先了解下单例模式的四大原则 构造方法私有 以静态方法或枚举返回实例 确保实例只有一个 尤其是多线程环境 确保反射或反序列化时不会重新构建对象 饿汉模式 饿汉模式在类被初始化时就创建对象 以空间换时间 故
  • java中Synchronized和Lock的区别

    Synchronized和Lock的区别 原始构成 synchronized关键字属于JVM层面的 通过monitorenter monitorexit指令实现 底层是通过monitor对象来完成 其实wait notify等方法也依赖mo
  • 线程通信基础示例(synchronized 与 Lock + Condition实现线程通信)

    目录 一 synchronized 实现线程通讯 代码示例 二 Lock Condition 实现线程通讯 代码示例 Lock Condition 实现线程通讯的优点 一 synchronized 实现线程通讯 什么是线程通讯 可以将线程分
  • 【2021最新版】Java多线程&并发面试题总结(108道题含答案解析)

    文章目录 JAVA并发知识库 1 Java中实现多线程有几种方法 2 继承Thread类 3 实现Runnable接口 4 ExecutorService Callable Future有返回值线程 5 基于线程池的方式 6 4 种线程池
  • 上下文切换理解以及减少方法

    并发编程面临着上下文切换 死锁等问题 尤其在少量数据的情况下 并发可能因为线程的创建和上下文切换的开销等问题 甚至比串行执行的速度更慢 文章目录 上下文切换定义 例子理解 减少上下文切换的方法 无锁并发编程 CAS算法 使用最少线程 协程
  • MPI与main()程序中的其他函数执行次数

    我原先以为只有在MPI代码区域 即MPI Init argc argv 到MPI Finalize 中的代码才会涉及到进程通信的问题 但实际上在MPI区域外的代码依然受到影响 执行的次数与开启的进程数有关 为此可以使用MPI 秩 rank
  • Java并发之锁

    Java并发之锁 一 临界区 二 线程安全 三 解决临界区线程安全问题 四 Java对象头 五 重量级锁 Monitor 5 1 synchronized 5 1 1 synchronized加锁流程 六 轻量级锁 6 1 轻量级锁加锁流程
  • shell编程笔记3--shell并发

    shell编程笔记3 shell并发 shell编程笔记3 shell并发 介绍 并发方法 1 简单后台方式 2 普通控制并发量方式 3 通过管道控制并发量 参考文献 shell编程笔记3 shell并发 介绍 在shell中适当使用并发功
  • Java 线程池的submit的使用与分析.md

    在Java5以后 通过Executor来启动线程比用Thread的start 更好 在新特征中 可以很容易控制线程的启动 执行和关闭过程 还能使用线程池的特性 上一篇我们介绍了线程池的基本用法和特性 我们用的最多的是ExecutorServ
  • 深入浅出 Java Concurrency (J.U.C)

    深入浅出 Java Concurrency J U C 转载 1 http www blogjava net xylz archive 2010 06 30 324915 html http www blogjava net xylz ar
  • Java 中的Lock锁对象(ReentrantLock/ReentrantReadWriteLock)详解

    目录 1 Lock Objects 详解 2 Java 中的 Lock Objects 的实现原理 3 ReentrantLock 详解 4 ReentrantReadWriteLock 详解 5 Lock锁的等待和唤醒 6 Lock 和
  • Semaphore 源码分析

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

    ThreadPoolExecutor源码解析 一 新建线程池的是构造方法 public ThreadPoolExecutor int corePoolSize int maximumPoolSize long keepAliveTime T
  • 锁介绍名词解释&&Lock && synchronized

    各种锁名词解释及应用 一 名词解释 1 乐观锁 VS 悲观锁 2 自旋锁 VS 适应性自旋锁 3 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁 4 公平锁 VS 非公平锁 5 可重入锁 VS 非可重入锁 6 独享锁 VS 共享锁 二
  • Java 多线程模式 —— Guarded Suspension 模式

    Part1Guarded Suspension 模式的介绍 我们只从字面上看 Guarded Suspension 是受保护暂停的意思 1Guarded Suspension 模式 在实际的并发编程中 Guarded Suspension

随机推荐

  • Ubuntu16.04下安装JDK1.8

    前提条件 拥有Ubuntu16 04环境 安装步骤 下载JDK安装包 下载版本 jdk 8u171 linux x64 tar gz 下载方式 云盘下载 云盘下载 提取码 7brp 官网下载 https www oracle com tec
  • python获取clickhouse数据表的全部列名称

    使用python获取的方法 import clickhouse connect client clickhouse connect get client host 127 0 0 1 def get col name table name
  • 详解:Char 和 varChar 之间的区别

    MySQL中的字符串有两个常用的类型 char和varchar 二者各有优势 下面我们来详细分析一下 通常在建表的时候对于String 类型的数据定义我们或许会很纠结 什么时候用char 什么时候用 varchar 呢 首先可以明确的是 c
  • 如何解决java.lang.NoClassDefFoundError--第二部分

    如何解决NoClassDefFoundError 第二部分 第一部分请看 http vipcowrie iteye com blog 1561291 本文面向的是JAVA初学者 建议你们自己编译和运行例子程序 本文包含了NoClassDef
  • c++单链表的简单实现(内含实现代码)

    考研报名等待之时闲来无事 写了一个简单的单链表 简单实现了以下功能 头插法建立单链表 按序遍历链表 单链表原地排序 不使用额外的空间 单链表按序删除 单链表原地倒置 附上代码如下 节点结构体定义 typedef int ElemType t
  • uniapp uview内置样式记录

    uview内置样式 文字省略 u line 1 u line 2 u line 3 u line 4 u line 5五个类名在文字超出内容盒子时 分别只显示一行 两行 三行 四行 五行 省略号 定位 uView内置了关于相对和绝对定位的两
  • 基于springboot的旅游信息管理系统完整源码

    基于springboot的旅游信息管理系统完整源码 技术栈 jdk1 8 mysql8 maven3 6 0 idea 功能模块 旅游路线 旅游景点 旅游酒店 旅游车票 旅游保险 旅游策略 订单管理 留言管理 数据分析等等 项目下载 htt
  • Matlab 如何生成一个[a,b]范围内随机整数的2种方法【已经解决】

    如何使用MATLAB生成一个 a b 范围内的随机整数 比如 随机生成 9 13 范围内的一个 或多个 整数 首先感谢 slandarer的指正 现已经更改 round 为四舍五入取整 而非向上取整 方法1为一个较为不错的方法 方法1 ra
  • 游戏开发-虚幻引擎天源了 [分享]

    https www unrealengine com zh CN 虚幻引擎4现在可供每个人免费使用 而且所有未来的更新都将免费 您可以下载引擎并将其用于游戏开发的各个方面 包括教育 建筑以及可视化 甚至虚拟现 实 电影和动画 当您发布游戏或
  • 计算机图形学入门(十六)-光线追踪(渲染方程)

    本部分主要介绍了渲染方程的逐步完善和简单的推导过程 从BRDF开始 到反射公式的推导再到渲染方程的完善 最后展示了实际渲染的例子 学习视频 GAMES101 现代计算机图形学入门 闫令琪 哔哩哔哩 bilibilihttps www bil
  • leaftlet 显示个性化图标、旋转图标

    1 引用leaftlet 高版本 比如1 8 3 在map js 中定义图标 L marker geo rotationAngle 270 icon L AwesomeMarkers icon icon awesomeIcon prefix
  • 创建匿名线程的5种方式

    package mythread 使用匿名内部类开启线程 public class Demo02anonymous thread public static void main String args 方式一 使用匿名内部类创建线程的子类对
  • java 是静态语言还是动态_java是动态语言还是静态语言?,

    java是动态语言还是静态语言 Java是动态语言还是静态语言 Java是一种静态语言 Java是编译时确定的变量类型 不能在运行时更改 在类型转换中也是强制的 例如 当大规模整数类型转换为小规模整数类型时 必须进行强转换 比如int必须强
  • 【TVM 学习资料】用 Schedule 模板和 AutoTVM 优化算子

    本篇文章译自英文文档 Optimizing Operators with Schedule Templates and AutoTVM 作者是 Lianmin Zheng Chris Hoge 更多 TVM 中文文档 访问 TVM 中文站
  • torch的GLU函数

    import torch nn as nn nn GLU GLU门控线性单元的描述 API
  • memcached查看所有的key

    memcached list all keys 1 打开命令窗口输入 gt telnet 127 0 0 1 11211 2 查找 输入 stats items 回车 显示各个slab 中 item 的数目和存储时长 最后一次访问距离现在的
  • 企业消息转发服务器,Python构建企业微信自动消息转发服务端

    目前有在项目分组 就小组成员中 微信群消息回复较多的情况下 想根据组来转发特定消息 包含文字 图片 语言等 在此只是自己实现仅供参考 可以根据自身需求修改更多功能 二 代码 2 1 企业微信相关信息 企业ID corpid 自建应用appi
  • ThinkPHP 的join关联查询不使用默认的表前缀

    关于ThinkPHP 的关联查询 官方文档是这样描述的 上述join函数中需要三个参数 分别是 join 要关联的 完整 表名以及别名 支持三种写法 写法1 完整表名或者子查询 gt 别名 写法2 完整表名 别名 写法3 不带数据表前缀的表
  • 100 个 Python 小项目源码,总有一个用得到

    学习 Python 会有这么一个阶段 太简单的程序看不上眼 复杂的开源项目又有点力不从心 这个时候 你就需要接触点简单的 Python 小项目来提升 Python 技能 碰巧 GitHub 上有这样一个项目 收集了 100 个简单的 Pyt
  • <并发编程>学习笔记------(一) 并发相关理论

    前面 并发编程可以总结为三个核心问题 分工指的是如何高效地拆解任务并分配给线程 同步指的是线程之间如何协作 互斥则是保证同一时刻只允许一个线程访问共享资源 并发相关理论 可见性 原子性和有序性 核心矛盾 CPU 内存 I O 设备的速度差异
Powered by Hwhale