1.类的静态代码框不是在类加载的过程运行而是在类初始化的时候运行
2.java重载的时候 如果方法名相同,参数类型,个数相同,返回类型不同会报错,应该是参数类型不同或者个数不同返回类型也可以不同满足其一
3.java虚拟机在类加载的准备阶段的时候会把类变量进行初始化,这里的初始化是把变量初始化为零值而非我们所定义的除非类变量被final修饰才会初始化为我们所定义的
4.判断两个类是否相等,只有在两个类在同一个类加载器下的前提下才有意义,如果都来自同一个Class但是由不同类加载加载,类必定不相同。
5 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
- 在java 动态分派和静态分派 重载是静态分派 就是传给我什么类型我就识别什么类型,如果没有就自动转换 重写是动态分配 识别真正new的对象
Java并发
https://www.cnblogs.com/dolphin0520/p/3920373.html
volatile关键字解析
有序性:即程序执行的顺序按照代码的先后顺序执行。举个简单的例子,看下面这段代码:
1 2 3 4 | int i = 0; boolean flag = false; i = 1; //语句1 flag = true; //语句2 |
上面代码定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么呢?这里可能会发生指令重排序(Instruction Reorder)。
下面解释一下什么是指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
在 JDK1.8 中对 HashMap 进行了优化: 当 hash 碰撞之后写入链表的长度超过了阈值(默认为8),链表将会转换为红黑树。
假设 hash 冲突非常严重,一个数组后面接了很长的链表,此时重新的时间复杂度就是 O(n) 。
如果是红黑树,时间复杂度就是 O(logn) 。
synchronized 关键字原理
实现原理: JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。
具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。
其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。
而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。
CAS(乐观锁实现的一种方式) 就是比较替换,比较现在的数据和以前未变之前是不是一样如果一样在进行数据的替换。
如果是基础类的自增操作可以使用 AtomicInteger 这样的原子类来实现(其本质是利用了 CPU 级别的 的 CAS 指令来完成的)。
volatile 关键字就是用于保证内存可见性,当线程A更新了 volatile 修饰的变量时,它会立即刷新到主线程,并且将其余缓存中该变量的值清空,导致其余线程只能去主内存读取最新值。
使用 volatile 关键词修饰的变量每次读取都会得到最新的数据,不管哪个线程对这个变量的修改都会立即刷新到主内存。
synchronized和加锁也能能保证可见性,实现原理就是在释放锁之前其余线程是访问不到这个共享变量的。但是和 volatile 相比开销较大。
volatile顺序性
需要volatile关键字的原因是,在并发情况下,如果没有volatile关键字,在第5行会出现问题。instance = new TestInstance();可以分解为3行伪代码
1.memory = allocate() //分配内存
2. ctorInstanc(memory) //初始化对象
3. instance = memory //设置instance指向刚分配的地址
上面的代码在编译运行时,可能会出现重排序从1-2-3排序为1-3-2。在多线程的情况下会出现以下问题。线程A在执行第5行代码时,B线程进来,而此时A执行了1和3,没有执行2,此时B线程判断instance不为null,直接返回一个未初始化的对象。
volatile 关键字只能保证可见性,顺序性,不能保证原子性。
读写锁
使用 ReentrantReadWriteLock ,同时维护一对锁:读锁和写锁。当写线程访问时则其他所有锁都将阻塞,读线程访问时则不会。通过读写锁的分离可以很大程度的提高并发量和吞吐量。
ConcurrentHashMap
1.8 中的 ConcurrentHashMap 数据结构和实现与 1.7 还是有着明显的差异。
其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。也将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。
其中的 val next 都用了 volatile 修饰,保证了可见性。
1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。
线程池
谈到了 SpringBoot,也可利用它 actuator 组件来做线程池的监控。
线程怎么说都是稀缺资源,对线程池的监控可以知道自己任务执行的状况、效率等。
关于 actuator 就不再细说了,感兴趣的可以看看这篇,有详细整理过如何暴露监控端点。
线程池隔离
线程池看似很美好,但也会带来一些问题。
如果我们很多业务都依赖于同一个线程池,当其中一个业务因为各种不可控的原因消耗了所有的线程,导致线程池全部占满。
这样其他的业务也就不能正常运转了,这对系统的打击是巨大的。
比如我们 Tomcat 接受请求的线程池,假设其中一些响应特别慢,线程资源得不到回收释放;线程池慢慢被占满,最坏的情况就是整个应用都不能提供服务。
所以我们需要将线程池进行隔离。
通常的做法是按照业务进行划分:
比如下单的任务用一个线程池,获取数据的任务用另一个线程池。这样即使其中一个出现问题把线程池耗尽,那也不会影响其他的任务运行。
线程通信
wait() 、notify()、notifyAll()完成线程的通信
- wait() 、notify()、notifyAll() 调用的前提都是获得了对象的锁(也可称为对象监视器)。
- 调用 wait() 方法后线程会释放锁,进入 WAITING 状态,该线程也会被移动到等待队列中。
- 调用 notify() 方法会将等待队列中的线程移动到同步队列中,线程状态也会更新为 BLOCKED
- 从 wait() 方法返回的前提是调用 notify() 方法的线程释放锁,wait() 方法的线程获得锁。
join方法是让调用该线程执行完毕在执行主线程
在 join 线程完成后会调用 notifyAll() 方法,是在 JVM 实现中调用,所以这里看不出来。
主线程可以根据修改共享内存的条件来终止子线程。反正子线程也可以这样做
类加载机制
双亲委派模型中除了启动类加载器之外其余都需要有自己的父类加载器
当一个类收到了类加载请求时: 自己不会首先加载,而是委派给父加载器进行加载,每个层次的加载器都是这样。
所以最终每个加载请求都会经过启动类加载器。只有当父类加载返回不能加载时子加载器才会进行加载。
双亲委派的好处 : 由于每个类加载都会经过最顶层的启动类加载器,比如 java.lang.Object这样的类在各个类加载器下都是同一个类(只有当两个类是由同一个类加载器加载的才有意义,这两个类才相等。)
如果没有双亲委派模型,由各个类加载器自行加载的话。当用户自己编写了一个 java.lang.Object类,那样系统中就会出现多个 Object,这样 Java 程序中最基本的行为都无法保证,程序会变的非常混乱。
强引用
强引用是指在程序代码中普遍存在的,类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用(Soft Reference)
用来描述一些还有用并非必要的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列入回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。JDK 1.2之后,提供了SoftReference类来实现软引用
使用软引用后,原本由于堆内存溢出而无法正常执行的代码段“正常的”执行成功;
但是,当我们访问早期创建的那些对象时,却报java.lang.NullPointerException异常,说明早期创建的对象已经被垃圾收集器回收了。
弱引用(WeakReference)
弱引用也是用来描述非必要对象的,但是他的强度比软引用更弱一些,被软引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。JDK 1.2之后,提供了WeakReference类来实现弱引用。
基于redis的分布式锁
为什么选用redis作为分布式锁呢?
因为redis首先可以作为一个公用的区域,而且redis是一个单线程的数据库,在redis的读写不需要考虑并发过程。
关键1:
SETNX key val
当且仅当key不存在时,set一个key为val的字符串,返回1;若key存 在,则什么都不做,返回0。
关键2:
expire key timeout
为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避 免死锁。
加锁的时候传入锁的名称然后根据uuid随机生成一个随机数作为这个锁value,设置在获取锁的时候的阻塞时间。和过期时间,防止死锁,自动释放锁。
当然在释放锁的时候根据这个value来判断释放跟锁的value是否相同来释放锁机制。
应用限流
计数器算法就是在一定时间内的并发量达到一定的阈值,就拒绝接受请求
滑动窗口,就是计数器算法的更精确版,相当于把一段时间分成若干块来区分。根据诺干段时间来进行判断.
漏桶算法,就是指以恒定的速率进行输出,用一个桶来装入,法力的请求,如果溢出就丢弃
令牌算法,就是指通里面放着令牌,一个请求可以支持拿多个令牌,来表示,令牌。可以处理流量突然的增大
SpringAOP的实现原理
Springaop的实现原理不过就是动态代理,
动态代理分为两种,jdk,和CGLIB两种
Jdk代理,实现了代理的被代理接口,实现invocationhander
CGLIB 是被代理类的子类。所以CGLIB实现代理,被代理类不能是final
mySQL索引原理
B+树,B+树就是有三个节点,放的是磁盘块,小的在左边,中间的在中间,大的在右边
如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
- 索引字段要尽量的小:通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
2.索引的最左匹配特性(即从左往右匹配):当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
#我们可以在创建上述索引的时候,为其指定索引类型,分两类
hash类型的索引:查询单条快,范围查询慢
btree类型的索引:b+树,层数越多,数据量指数级增长(我们就用它,因为innodb默认支持它)
#不同的存储引擎支持的索引类型也不一样
InnoDB 支持事务,支持行级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
MyISAM 不支持事务,支持表级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
Memory 不支持事务,支持表级别锁定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
NDB 支持事务,支持行级别锁定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
Archive 不支持事务,支持表级别锁定,不支持 B-tree、Hash、Full-text 等索引;