一、MySql篇
聚集索引和非聚集索引
b树,b+树,b*
数据库索引是什么
MySql的InnoDB和MyISAM
SQL server性能优化
自我总结:
- 聚簇索引决定表中每条数据物理存储的位置(磁盘中的位置,以盘块进行分块存储)。
- 非聚簇索引性能的好坏由读取盘块的数量决定(即聚簇索引最初决定的存储位置)
- InnoDB是聚簇索引,支持事务,外键,行级锁,崩溃可恢复
- MyISQM是非聚簇索引,读多写少时可使用
(1)在主键上建立聚集索引,在fariq上建立非聚集索引: 在id上建立聚集索引,那么磁盘中每条数据就是以id顺序排序来存储, 相同时间fariqi的数据被随机存储到不同盘块中, 在进行时间fariqi查找时会读取大量盘块。 select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi> dateadd(day,-90,getdate()) 用时:53763毫秒(54秒) (2)将聚合索引建立在日期列(fariqi)上: 在fariqi上建立聚簇索引,那么盘块中每条数据就是以fariqi顺序排序来存储, 相同时间fariqi的数据被存储到相邻的盘块中, 在进行时间fariqi查找时只需读取相邻盘块, 当数据量很大时,读取对的盘块较(1)会少的多得多。 select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi> dateadd(day,-90,getdate()) 用时:2423毫秒(2秒)
Mysql读取未提交 - 出现脏读写排他锁
Mysql读取已提交 - 解决脏读 写用排他锁(行级锁)(select * from user where id = ‘1’ for update可获得排他锁) + 读用MVCC(记录两份数据:修改的数据和修改前数据)
Mysql可重复读 - 解决脏读和不可重复读 写用排他锁 (行级锁)(select * from user where id = ‘1’ for update可获得排他锁)+ 读用MVCC(记录多份数据:多了修改id字段和删除id字段) + 间隙锁
Mysql的MVCC理解 、 MVCC的事务一:读(中间插入事务二:写)再写
自我总结:
事务没提交,期间获得锁在事务完成时才会释放。
READ UNCOMMITTED隔离级别下, 读不会加任何锁。而写会加排他锁,并到事务结束之后释放。
READ COMMITTED和REPEATABLE READ写数据是使用排他锁, 读取数据不加锁而是使用了MVCC机制
利用Gap Locks间隙锁可以阻止其它事务在锁定区间内插入数据,因此解决了幻读问题
幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。(可重复读隔离级别下会读取互斥锁并修改行数据,可是新插入数据行并没有被争夺,所以期间是可以插入的。)
自我总结:
Mysql为什么不直接使用刷新数据到磁盘,而是使用日志记录?
1.方便事务回滚
2.直接刷新数据到磁盘,会到磁盘找到指定盘块在进行写入,相当于随机IO;而使用日志的形式将信息刷新到指定磁盘位置进行追加写入,相当于顺序IO;提高了效率。
二、Java篇
基础自我总结:
1.Java文件编译解释过程
编译程序是整体编译完了,再一次执行,而解释程序是边解释边执行。
Java编译器:将Java源文件(.java文件)编译成字节码文件。
Java解释器:是JVM的一部分。它每翻译一行程序叙述就立刻运行,然后再翻译下一 行,再运行,如此不停地进行下去。
JIT编译器:是指一种在运行时期把字节码编译成原生机器码的技术,一句一句翻译源代码,但是会将翻译过的代码缓存起来以降低性能耗损。
2.使用Java不进行开发,只下载JRE可以吗?
答:如果要使用JSP部署Web应用程序,需要使用 JDK 来编译 servlet(即需要java.exe编译器来编译.java文件)。
3.StringBuilder比StringBuffer使用的多。web多用户访问场景
问题:并发情况下统计访问指定页面的总人数及信息存储数据库???如何优化???
我的思路:定义线程安全的单例模式统计总人数,使用队列的方式存储用户信息,达到一定数据量 / 每隔一秒开启新线程刷新到数据库。
ConcurrentHashMap1.7分段锁,由于hashmap加入红黑树,取消segments分段锁,改为1.8的hashmap(分段锁)+CAS+synchronized实现
HashMap,1.8加入红黑树
HashMap和HashTable的区别
自我总结:
-
ArrayList
不是线程安全
内部采用:Object[]数组
扩容机制:int newCapacity = oldCapacity + (oldCapacity >> 1);
将新容量更新为旧容量的1.5倍 -
Vector
线程安全:public方法都添加了synchronized关键字
内部采用:Object[]数组 -
LinkedList:
不是线程安全
内部采用 :双向链表
用来做栈:push()
头部追加;pop()
删除并返回头部,peek()
获取头部资源
队列:offer()
尾部追加,poll()
删除并返回头部,peek()
获取头部资源 -
HashMap:(哈希函数, 除留余数法为什么使用质数&二进制均为1最好)
不是线程安全:多线程下可能会产生死锁
内部采用:顺序表+链表+红黑树
默认容量:16
负载因子:0.75f
节点大于8转成红黑树结构,以减少搜索时间
扩容:newThr = oldThr << 1
,二倍扩容
支持键值为null,不过不建议。
hash扰乱:hash(),可以减少碰撞。 -
Hashtable:
线程安全:每个方法中都加入了Synchronize方法
不允许键值为null
扩容:容量变为原来的2n+1 -
ConcurrentHashMap:
线程安全:使用通过高四位分段锁,每个段又是一个hash表
扩容机制:只是扩容自己分段内的数据,不用全部重排数据。 -
HashSet:
底层使用HashMap的key来实现,value为 static final Object 对象(为什么不用null)
三、进程与线程
1.图解
- 程序计数器:线程切换后能恢复到正确的执行位置。
- 虚拟机栈: 为虚拟机执行 Java 方法 (也就是字节码)服务。(每个Java方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈和常量池引用等信息。)从方法调用直至完成的过程,就对应着一个栈帧在Java虚拟机中入栈和出栈的过程。
- 本地方法栈:为虚拟机使用到的 Native 方法服务。
- 堆:是进程中最大的一块内存,主要用于存放新创建的对象。
- 方法区:用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。(中的class文件常量池包含字面量和符号引用)
- 运行时常量池:常见的几种常量
2.线程状态
3.wait()和sleep()区别
- 相同点都可以暂停线程的执行。
- sleep()没有释放锁,wait()释放了锁。
- wait()主要进行线程间交互/通讯,sleep()通常暂停服务。
- wait()被调用后,不能自动苏醒,需要其它线程notify()唤醒。sleep()指定时间完后会主动苏醒。
sleep(0)的作用:Java采用抢占式,根据优先级调度线程,sleep(0)让当前线程暂时放弃cpu,相当于一个让位动作。
Java并发
- volatile关键字
- 可见性
- 防止指令重排
- 不保证原子性
- ReentrantLock和Synchronized的区别
- ThreadLocal:让每个线程有自己的专属变量,实际使用
- 乐观锁和悲观锁 ABA问题–使用juc.atomic原子类解决
- Synchronized 1.6版本之后的偏向锁、轻量级锁、重量级锁。
JUC包
-
AQS核心:
- 同步状态private volatile修饰
- 线程的阻塞和唤醒 LookSupport
- 等待双向队列:实现并发情况插入到等待队列中使用 自旋+CAS实现队尾插入
-
公平锁和非公平锁:关键在于tryAcquire()中判断队列中是否有其它线程排在当前线程前
-
CountDownLatch是使用countDown()进行减计数方式,不会对线程进行阻塞。可以它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。不可重复利用。
-
CyclicBarrier是 使用await()进行加计数方式 ,会对线程进行阻塞。当线程到达一定数量释放所有等待线程并触发指定的一个线程。可重复利用。
-
Atomic原子类:利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销
-
CopyOnWriteAarrayList :在新数组完成之前(新数组创建过程中)进行的读操作不会收到影响,创建完成之后会有影响。可以尝试加ReentrantReadWriteLock
线程A和线程B对 arr 进行操作
CopyOnWriteArrayList<String> arr = new CopyOnWriteArrayList<>();
arr.add("1");
arr.add("2");
arr.add("3");
线程A 首先读取index = 2的元素,等待1000ms,再读取index = 2 的元素
new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread() + arr.get(2));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String s = arr.get(2);
System.out.println(Thread.currentThread() + s);
}
}).start();
线程B 首先读取index = 2的元素,再移除index = 2的元素(相当于在线程A的两个操作中间插入的操作)。
new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread() + arr.get(2));
String s = arr.remove(2);
System.out.println(Thread.currentThread() + s);
}
}).start();
结果:
四、
五、JVM
自我总结:
类加载过程:参考一,参考二
1.类加载检查:检查常量池中是否有该符号引用,并判断该符号引用是否被加载连接初始化过。
- 1.1加载:获取类的二进制字节流。步骤
- 1.2连接
- 1.2.1.验证:是否能被当前JVM加载。
- 1.2.2 准备:进行内存分配的仅包括类变量(static)设置零值。(方法区)
- 1.2.3 解析:将常量池内的符号引用(class常量)替换为直接引用(动态常量)的过程。
- 1.3初始化:为静态变量赋值为初始值并执行静态块代码。
2.分配内存:为新生对象在堆中分配内存(指针碰撞和空闲列表)。
3.初始化零值:虚拟机将分配到的内存空间都初始化为零值(不包括对象头)。
4.设置对象头:这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希 码、对象的 GC 分代年龄等信息。
5. 执行init方法:把对象按照程序员的意愿进行初始化。
符号引用:以一组符号来描述所引用的目标,引用的目标并不一定已经加载到内存中。
直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能简介定位到目标的句柄。引用的目标必定已经在内存中存在。
JVM垃圾回收
使用场景
HotSpot的Client和Service模式
- 单核服务器、Client模式:Serial + Serial Old
- 注重吞吐量,高效利用 CPU,需要高效运算且不需要太多交互:Parallel Scavenge + Parallel Old
- 重视服务器响应速度,要求系统停顿时间最短:CMS + ParNew
- 要求尽可能可控 GC 停顿时间;内存占用较大的应用: G1
吞吐量 = 运行用户代代码时间/(运行用户代码时间+垃圾收集时间)
server启动慢,占用内存多,执行效率高,适用于服务器端应用;
client启动快,占用内存小(默认情况下不进行动态编译),执行效率没有server快,适用于桌面应用程序。
-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:+UseParNewGC:在新生代使用并行收集器
-XX:+UseParallelGC :新生代使用并行回收收集器,更加关注吞吐量
-XX:+UseParallelOldGC:老年代使用并行回收收集器
-XX:ParallelGCThreads:设置用于垃圾回收的线程数
-XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:ParallelCMSThreads:设定CMS的线程数量
-XX:+UseG1GC:启用G1垃圾回收器
- 阻塞IO
public class IOServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8000);
// (1) 接收新连接线程(开启一个线程用于监听8000端口请求)
new Thread(() -> {
while (true) {
try {
//用户线程(指向核心态的socket):建立连接(三次握手)
Socket socket = serverSocket.accept();
// (2) 每一个新的连接都创建一个线程,负责读取数据
new Thread(() -> {
try {
byte[] data = new byte[1024];
InputStream inputStream = socket.getInputStream();
while (true) {
int len;
//(2)创建的用户线程经系统调用进入内核态:(2)线程阻塞,在内核空间准备好数据,(2)线程系统调用进入内核态并将好的数据拷贝到用户空间。
while ((len = inputStream.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
}
} catch (IOException e) {
}
}).start();
} catch (IOException e) {
}
}
}).start();
}
}
后续会继续添加