学习笔记

设计模式

-    

1. 合成与聚合
- 合成 :一种强拥有关系,部分和整体的生命周期相同(学生和班级)
- 聚合 : 整体与部分的关系,整体和部分的生命周期可以不通过(人和器官)
2. 设计模式

  • 适配器模式:
    • 持有另外一个实际动作类引用,对外暴露其他接口以实现功能的转接。
  • 桥接模式 :
    • 主要作用 :将两个不同维度的事物进行分离,当一个具体的事物涉及到了两个维度就可以使用桥接模式。例如电脑的类型和品牌
    • 抽象类及抽象类实现、抽象类持有具体分离维度接口的引用,且分离维度接口有多个不同的实现。
  • 装饰者模式 :
    • 用于动态的扩展类,对功能进行增删。经典的就是java io
    • A、B均持有具有共性动作的接口,A持有B的引用,实现A动态扩展B的功能。
  • 门面模式 :
    • 门面持有子系统的引用,统一完成一些动作,东里不动外。看场景使用。
  • 享元模式:
    • 一般配合工厂模式使用,如jdbc连接池,资源复用
  • 工厂模式 :
    • 工厂类根据入参返回具体的实例。
  • 建造者模式 :

  • 原型模式 :

    • CloneAble 复写 clone 方法,依赖super.clone()
    • 深克隆需要使用二进制输入再读出对象。

    • 浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。

    • 深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。

  • 责任链模式 :

    • 层层传递 : 一个对象持有下个对象 后继 ,以实现类似于工作流等功能
      设计上首先需要一个公共的handler,handler持有自身引用,设定后继的方法
      多个实现类分别实现接口,并且设定后继,通过引用传递的方式减少耦合。
  • 观察者模式:

数据结构

-     

1. 堆结构 : 先进先出
- (满)完全二叉树
- 除叶子节点以外的其他节点都必须具备左右孩子节点
- 完全二叉树 :
- 不要求一定具备左右孩子节点,但一定是按照顺序的,左节点无孩子节点,右将不能够具备孩子节点
- 堆类型 :
- 大根堆、小根堆 ->指头节点是否必须是整棵树数值最大,下面的子层级也将依照该规则
- B树和B+树
- B树叶子节点前可返回,节点即挂载指针也挂载数据。同一个磁盘块索引范围小,需要更多的io
- B+ : 非叶子节点只挂载索引,相同磁盘块索引范围更宽,以减少io,另外叶子节点存的是范围值。
- B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因

  1. 队列 :
    • PriorityQueue
    • PriorityQueue 实现队列
      • 判断队列中是否存在元素,没有返回null,存在则删除头节点,将尾节点替换并调整结构,弹出删除的头节点,以此满足先进先出。
    • Deque
      • 双端队列,即可当做栈后端进出,又可当做堆,首端进出

JMM

    -    

1. 线程间通信的两种方式 :
1.1 共享变量 1.2 通知方式
- java内存模型是共享内存的并发模型,线程间主要通过读写共享变量来实现隐式通信
- volatile : 保证变量每次都能强制刷新到主存中。

  1. 重排序 :
    • 为了提高性能,编译器和处理器会对指令进行重排序。保证程序的执行结果为前提。
    • 目的 : 提高并行度。
    • 重排序类型 : 1. 编译器优化重排序 2.处理器重排序 : 指令级、内存级
    • 基于依赖的优化 : 保证单线程的执行结果,只要没有依赖关系则会对执行重拍以达到最大并发度,实现性能的优化。

并发

- 

synchronized

  • 对象锁:monitor
    • 执行代码时需要先获得monitorenter 执行完毕后执行monitorexit 退出,其他线程进入后如果获取不到monitor则进入block状态
    • 每个对象拥有计数器,获得锁时计数器+1,释放锁时计数器-1以达到平衡。Synchronized先天具有重入性。
    • 特性: 同一时刻只有一个线程能够获得对象的监视器monitor(互斥性)

cas( compare and swap 比较交换)

  • 无锁操作,是一种乐观锁策略: 出现冲突时使用cas不断的重试,直到不再存在冲突为止。
  • VON :V 内存地址存放的实际值;O 预期的值(旧值);N 更新的新值
    ###### 当V和O相同时,也就是说旧值和内存中实际的值相同表明该值没有被其他线程更改过,即该旧值O就是目前来说最新的值 了,自然而然可以将新值N赋值给V
    • cas问题:
      1. ABA问题: 版本号控制,类似于数据库乐观锁的实现
      2. 自旋时间过长问题:性能损耗
      3. 只能保证一个共享变量的原子操作
    • java对象头
  • Java对象头里的Mark Word里默认的存放的对象的Hashcode,分代年龄和锁标记位

    锁只能升级不能降级

    • 偏向锁: 对象头存储位,通过尝试修改锁的对象引用来获取锁,以减少cas自旋获取锁的过程,以此来提高性能

全局安全点:在该时间节点上没有正在执行字节码


- 

volatile

  • 实现原理 :
    • lock 指令 :
      1. 将当前处理器的缓存行的数据写回到系统内存
      2. 这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效
    • volatile : 通过插入内存屏障禁止指令重排序

final

-

1. final 修饰变量
- 变量
- 类变量:必须要在静态初始化块中指定初始值或者声明该类变量时指定初始值,而且只能在这两个地方之一进行指定;
- 实例变量:必要要在非静态初始化块,声明该实例变量或者在构造器中指定初始值,而且只能在这三个地方进行指定。
- 当final修饰基本数据类型变量时,不能对基本数据类型变量重新赋值,因此基本数据类型变量不能被改变。
- 而对于引用类型变量而言,它仅仅保存的是一个引用,final只保证这个引用类型变量所引用的地址不会发生改变,即一直引用这个对象,但这个对象属性是可以改变的。
2. final 修饰方法
- 父类final修饰的方法不能被重写
- final修饰的方法可以被重载
3. final 修饰类
- 被final修饰的类不能够被继承,当一个类不希望被子类修改时就可以使用final修饰

jdk中的八个包装类和String类均为final 修饰的不变类。

多线程中的final

禁止对final域的写重排序到构造函数之外

  • 编译器会在final域写之后,构造函数return之前,插入storestore内存屏障。

关于final重排序的总结

按照final修饰的数据类型分类:

基本数据类型:

final域写:禁止final域写与构造方法重排序,即禁止final域写重排序到构造方法之外,从而保证该对象对所有线程可见时,该对象的final域全部已经初始化过。
final域读:禁止初次读对象的引用与读该对象包含的final域的重排序。
引用数据类型:

额外增加约束:禁止在构造函数对一个final修饰的对象的成员域的写入与随后将这个被构造的对象的引用赋值给引用变量 重排序

final修饰的基本数据类型和引用数据类型,所有的变量都必须在对象构造完成后完成初始化,以保证对象对所有线程可见时,final域已经全部初始化过。


java内存模型中的8个原子操作

-
  1. lock(锁定):作用于主内存中的变量,它把一个变量标识为一个线程独占的状态;
  2. unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  3. read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便后面的load动作使用;
  4. load(载入):作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存中的变量副本
  5. use(使用):作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作;
  6. assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作;
  7. store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送给主内存中以便随后的write操作使用;
  8. write(操作):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

-

AbstractQueuedSynchronizer (AQS)队列同步器

AQS提供的模板方法可以分为3类:
  1. 独占式获取与释放同步状态;
  2. 共享式获取与释放同步状态;
  3. 查询同步队列中等待线程情况;
AQS基本结构及实现原理 :
  1. 内部为Node为双向队列,AQS持有node链表的前驱和后继以实现对队列的控制。带头节点的链式存储结构。
    故头节点的初始化时机为尾节点为null时,调用enq()方法,内包含cas的自旋操作。自旋主要是对cas的重试。
  2. 实际队列增加节点时,判断tail节点是否为null,如果为null则说明是新的节点,否则将调用unsafe的原子CAS操作
    进行节点的增加。

独占锁获取和释放总结:

  1. 线程获取锁失败,线程被封装成Node进行入队操作,核心方法在于addWaiter()和enq(),同时enq()完成对同步队列的头结点初始化工作以及CAS操作失败的重试;
  2. 线程获取锁是一个自旋的过程,当且仅当 当前节点的前驱节点是头结点并且成功获得同步状态时,节点出队即该节点引用的线程获得锁,否则,当不满足条件时就会调用LookSupport.park()方法使得线程阻塞;
  3. 释放锁的时候会唤醒后继节点;
    • 总体来说:
      • 在获取同步状态时,AQS维护一个同步队列,获取同步状态失败的线程会加入到队列中进行自旋;移除队列(或停止自旋)的条件是前驱节点是头结点并且成功获得了同步状态。在释放同步状态时,同步器会调用unparkSuccessor()方法唤醒后继节点
独占锁同一时间只允许一个线程获得锁,共享锁则允许多个线程同时获得锁,并发访问资源。内部都是基于cas来保证资源并发的安全性。

ReentrantLock : 可重入锁

  1. 重入: 指当前线程已经获取了该资源的锁,再次获取的时候不会被阻塞。
    • 对非公平锁来讲,核心方法为 :nonfairTryAcquire
      • 判断当前资源的状态,如果未被任何线程持有,则设置当前锁被当前线程获取,否则检查是否是当前线程,如果持有锁的为当前线程,则计数+1,不会阻塞。
    • 依照重入锁的实现方式 : 释放锁的过程则需要计数归0时锁才是真正的得到了释放。
  2. 公平锁与非公平锁区别 :
    • 公平锁与非公平锁 : 见文知意,是否按照顺序来获取锁,非公平锁则支持线程的竞争获取锁。而公平锁则是需要按照队列顺序以此获得资源的锁。
    • 实现原理 : 公平锁会基于AQS检查当前线程节点前是否存在节点,如果存在说明有更加优先的等待资源,则当前线程不能够获取竞争资源的锁。
公平锁 VS 非公平锁
  1. 公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。

  2. 公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

ReentrantReadWriteLock : 读写锁

读写锁允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞 。
写锁 : 写锁为独占锁
  • 锁获取方法tryAcquire,其主要逻辑为:
    • 当读锁已经被读线程获取或者写锁已经被其他写线程获取,则写锁获取失败;否则,获取成功并支持重入,增加写状态。
读锁 : 读锁为共享锁,可同时被多个线程获取

- 

CopyOnWriteArrayList

  • 写入前先将原集合copy一份进行写入,写入完成后再将引用的指针指向新的集合。适用于读多写少的场景,牺牲数据的实时性来满足数据的最终一致性。
get 方法
  • 方法和单线程下的程序一致,没有任何线程安全控制。读线程只会读取数据,不会对数据做出修改。
add 方法
  • 写方法使用的是ReentrantLock 独占锁,保证写的线程在同一时间只能有一个。
总结 :
  1. 采用ReentrantLock,保证同一时刻只有一个写线程正在进行数组的复制,否则的话内存中会有多份被复制的数据;
  2. 前面说过数组引用是volatile修饰的,因此将旧的数组引用指向新的数组,根据volatile的happens-before规则,写线程对数组引用的修改对读线程是可见的。
  3. 由于在写数据的时候,是在新的数组中插入数据的,从而保证读写实在两个不同的数据容器中进行操作
缺点 :
  1. 内存占用问题 : 需要copy数组来实现读写分离
  2. 数据一致性问题 : 牺牲短暂的读不一致来保证数据的最终一致性。不适合对读实时性要求较高的场景。

ThreadLocal

  • 数据结构上为每一个线程分配一个自己的Map,人手一份来实现变量的共享。
  • 内存溢出问题 :
    • threadlocal的key为弱引用,即GC触发时会回收key,key为当前Threalocal的实例
    • 当key被回收时,value并不能伴随着回收,因为当前线程与ThreadLocalMap是强引用关系,只要当前线程没有结束,
      value将一直存在。另外key被回收时,key为null的value将永远存在,且为强引用链。

- 

BlockingQueue

ArrayBlockingQueue
  • 基于数组的有界阻塞队列
  • 可用于典型的生产者消费者模式,也可以实现公平性,但是公平性会降低吞吐量。
LinkedBlockingQueue
  • LinkedBlockingQueue是用链表实现的有界阻塞队列,同样满足FIFO的特性,与ArrayBlockingQueue相比起来具有更高的吞吐量,为了防止LinkedBlockingQueue容量迅速增,损耗大量内存。通常在创建LinkedBlockingQueue对象时,会指定其大小,如果未指定,容量等于Integer.MAX_VALUE
PriorityBlockingQueue
  • 支持优先级的无界阻塞队列
SynchronousQueue
  • SynchronousQueue每个插入操作必须等待另一个线程进行相应的删除操作
  • 因此实际上不存储任何数据,可通过构造函数执行其公平性
LinkedTransferQueue
  • 链表数据结构构成的无界的队列。
LinkedBlockingDeque
  • 基于链表数据结构的有界阻塞双端队列
DelayQueue
  • 是一个存放实现Delayed接口的数据的无界阻塞队列,只有当数据对象的延时时间达到时才能插入到队列进行存储

-

serialVersionUID 作用

  • 保证序列化和反序列化的兼容
  • 类的变动需要让序列化的机制感知到是否是合理的变动,否则class变动将会出于安全的考虑抛出异常。

-  

IO模型及IO复用 :

  • IO的五种模型 :

    1)阻塞I/0

    2)非阻塞I/O

    3)I/O复用

    4)事件(信号)驱动I/O

    5)异步I/O

  • 系统调用 : 进程想要获取磁盘中的数据就要告诉内核,因为只有内核能够和硬件打交道。

一次 IO 的完成步骤 :
  1. 磁盘把数据装载到内核空间。
  2. 内核的内存空间将数据copy到用户的工作空间 – >(IO即在此处产生)

  1. 进程向内核发起一个系统调用
  2. 内核接收到系统调用,知道是对文件的请求,于是告诉磁盘,把文件取出来
  3. 磁盘接收到内核的命令,把文件载入到内核的内存空间
  4. 内核的内存空间接收到数据之后,把数据copy到用户进程的内存空间 (此处是Io发生点)
  5. 进程的内存空间得到数据后,给内核发送通知
  6. 内核把接收到的通知回复给进程,此过程唤醒进程,然后进程得到数据,进行下一步操作

1)阻塞IO

  • 进程一直在等待内核将磁盘中的数据copy到进程的内存空间并通知的过程。

2)非阻塞IO

  • IO知道操作将会耗时,于是告诉进程去做其他事,进程会不断的发起系统调用,直到得到内核的反馈。

3)IO复用

  • 由select 代替进程发起系统调用,阻塞发生在select 上而没有直接发生在进程上。

IO多路复用 : 通过某种机制,可以监视多个描述符。能够通知到进程进行读或者写操作。

正常的IO是阻塞等待描述符符合期望值时唤醒进程进行相应的操作。而IO多路复用则不需要进程去等待结果,IO会主动告诉进程去做其他的事,在监视器监视到描述符就绪时再通知进程获取数据。

IO的事件驱动
  1. 水平触发的事件驱动 : 内核通知进程来获取数据,如果进程没有来读取数据,内核需要一次又一次的通知进程。
  2. 边缘触发的事件驱动 : 内核只通知进程一次来获取数据,存在超时时间,进程只要在超时时间过去之前都可以过来读取数据。
异步IO基于事件回调来完成对内核准备好的数据的读取。
select poll epull
  • 主进程划分N个子进程,每个子进程创建线程用于处理用户请求。

    fd : 用于存放句柄描述符的数据结构 。

    1. select : 需要遍历fd,频繁的从用户空间到内存空间同步描述符状态。默认1024连接限制。
    2. poll与select 本质上没什么区别 : 只不过poll采用的是链表结构,没有数量上的限制。poll_fd而不是select 的fd_set。
    3. epoll 只需要判断就绪链表是否为空。epoll内部采用红黑树结构。

-

TCP/IP 三次握手与四次挥手

三次握手
  1. 客户端 – > 服务端 : 开门,我要进来了!
  2. 服务端 – > 客户端 : 可以,我去给你开门!
  3. 客户端 – > 服务端 : 好的,我进来了!
四次挥手
  1. 客户端 – > 服务端 : 我要走了!
  2. 服务端 – > 客户端 : 知道了,我送你出门!
  3. 服务端 – > 客户端 : 我要关门了!(等着客户端走)
  4. 客户端 – > 服务端 : 知道了,我走了!

-    

Hessian 原理分析

  • Hessian 是由 caucho 提供的一个基于 binary-RPC 实现的远程通讯 library

- 

锁的类型及升级 :

  1. 锁的类型 :

    1) 偏向锁 : 为了减少获得锁及释放锁的性能损耗引入。偏向锁一旦获取后会存入当前线程ID到对象头,下次进入和退出同步块时将不需要花费
    cas来获取和释放锁。

偏向锁的获取

当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁),如果没有设置,则使用CAS竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

偏向锁的释放需要等到全局安全点,故存在stop-world现象.

2) 轻量级锁 : 现在栈帧中创建用于存储锁记录的空间,并将当前对象头存入锁记录。cas将对象头中的mark word 替换为指向锁记录的指针。
成功则获得锁,否则cas自旋不断尝试,自旋仍然失败时升级为重量级锁。

3) 重量级锁 : 在JVM中又叫对象监视器,monitor,包含竞争锁的队列用于互斥。

4) 公平锁
5) 非公平锁
6) 重入锁
7) 自旋锁

  1. 锁的几种状态 : 无锁状态、偏向锁、轻量级锁、重量级锁

java对象头 : http://luojinping.com/2015/07/09/java%E9%94%81%E4%BC%98%E5%8C%96/

java对象头 :
- MarkWord : 存储对象的hashCode及锁标记位、分代年龄
单独存放是否为偏向锁标记


- 

Tomcat 请求处理过程 : https://www.ibm.com/developerworks/cn/java/j-lo-tomcat1/

Tomcat 核心组件

1) Connector : 负责接收浏览器发过来的tcp连接请求,创建一个线程把生产的request和response对象传给处理这个请求的线程。
2) Container : 接收connector传入的线程,将request和response交给servlet处理。
- Engine :
- Host :
- Context :
- Wrapper :


数据库

事务的隔离级别

  • 未提交读:一个事务可以读取另一个未提交的数据,容易出现脏读的情况。

  • 读提交:一个事务等另外一个事务提交之后才可以读取数据,但会出现不可重复读的情况(多次读取的数据不一致),读取过程中出现UPDATE操作,会多。(大多数数据库默认级别是RC,比如SQL Server,Oracle),读取的时候不可以修改。

  • 可重复读: 同一个事务里确保每次读取的时候,获得的是同样的数据,但不保障原始数据被其他事务更新(幻读),Mysql InnoDB 就是这个级别。

  • 序列化:所有事物串行处理(牺牲了效率)

mysql 索引

  • 优点 :避免全表扫描、关联查询能够根据索引逐层过滤,将需要查询的数量级降低
  • 缺点 :

    1. 索引的增多会降低写入的速度,在写入行数据时还需要索引做出相应的改变。因此对写入较多的数据表索引的数量决定这索引更新锁带来的负面问题。
    2. 索引需要占据磁盘空间,因此相比于没有索引的表来讲会更快的达到数据表的尺寸极限。

    因此当你不需要某个特定的索引来增加你的查询速度,就不要创建他。

索引创建方法

  1. 最适合有索引的数据列是那些在where 子句中、连接子句中、order by、group by中经常出现的数据列。
  2. 尽量对短小的值建立索引
  3. 对于字符串,如果前缀可以确定唯一,应制定索引的前缀长度,
  4. 充分利用最左边前缀。 复合索引不符合最左前缀规则则不会走索引。
  5. 适可而止,不要建立过多的索引。
    • 数据项更新后涉及到索引的更新及重新编排,多出的索引还会占据额外的磁盘空间。

优化方法

  1. 尽量使用数据类型相同的进行比较
  2. 索引列放入复杂的函数计算中,索引将会失效
  3. 左like会失效索引
  4. mysql有些语义上的规则不是特别严格,但尽量避免一些不必要的数据类型转换
  5. 尽量使用数值操作,少使用字符串操作,数值类型的操作会比字符串的操作快的多
  6. 如果使用”小”类型够用尽量不要使用”大类型”,这对于数据的索引效率及字符串的查询效率意义重大
  7. 尽量选择固定长度的数据列,如char和varchar能使用固定长度尽量使用固定长度的
  8. ENUM列在mysql内部会转化成一系列的固定数值,以提升检索效率
  9. 使用人造索引,计算出负责查询的hash值,以列的形式存入表中,但缺点是只适用于精确查找
  10. 尽可能的避免对大字段(BLOB、TEXT)的检索,可以考虑将大字段分表出去单独存储

聚簇索引和非聚簇索引的区别

  1. 聚簇索引叶子节点存放对应的数据,而非聚簇索引数据存放在堆中(实际叶子节点中存放的是指向对应数据块的指针),需要进行二次查询
  2. 聚簇索引是按照物理上的连续顺序存放,而非聚簇索引是按照逻辑顺序存放
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值