-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 323 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 323 KB
1
{"meta":{"title":"Jehu","subtitle":"","description":"","author":"Jehu Ren","url":"http://renjiahui.cn","root":"/"},"pages":[{"title":"about","date":"2021-01-11T18:33:20.000Z","updated":"2021-01-11T18:37:51.998Z","comments":false,"path":"about/index.html","permalink":"http://renjiahui.cn/about/index.html","excerpt":"","text":"很惭愧,做了一点微小的工作"}],"posts":[{"title":"List学习","slug":"java/List学习","date":"2021-02-18T09:02:57.000Z","updated":"2021-02-18T09:04:19.049Z","comments":true,"path":"2021/02/18/java/List学习/","link":"","permalink":"http://renjiahui.cn/2021/02/18/java/List%E5%AD%A6%E4%B9%A0/","excerpt":"","text":"ArrayList and LinkedList联系与区别线程安全:都是线程不安全 底层实现: arraylist 是数组,linkedlist是双向链表 复杂度问题:对于插入来说,若ArrayList直接add 添加到最后一个,则复杂度为O(1),若指定位置插入和删除元素则复杂度为O(n-i),而LinkedList采用链表,插入和删除的复杂度接近O(1)。 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。 ArrayList源码分析构造方法ArrayList有三种方式来初始化,构造方法源码如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647/** * 默认初始容量大小 */private static final int DEFAULT_CAPACITY = 10;private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/** *默认构造函数,使用初始容量10构造一个空列表(无参数构造) */public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}/** * 带初始容量参数的构造函数。(用户自己指定容量) */public ArrayList(int initialCapacity) { if (initialCapacity > 0) {//初始容量大于0 //创建initialCapacity大小的数组 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) {//初始容量等于0 //创建空数组 this.elementData = EMPTY_ELEMENTDATA; } else {//初始容量小于0,抛出异常 throw new IllegalArgumentException(\"Illegal Capacity: \"+ initialCapacity); }}/** *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回 *如果指定的集合为null,throws NullPointerException。 */public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; }} 以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。 add()方法12345678910/** * 将指定的元素追加到此列表的末尾。 */public boolean add(E e) { //添加元素之前,先调用ensureCapacityInternal方法 ensureCapacityInternal(size + 1); // Increments modCount!! //这里看到ArrayList添加元素的实质就相当于为数组赋值 elementData[size++] = e; return true;} 调用的ensureCapacityInternal()方法 123456789//得到最小扩容量private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 获取默认的容量和传入参数的较大值 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity);} 一定会调用的ensureExplicitCapacity方法123456789//判断是否需要扩容private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) //调用grow方法进行扩容,调用此方法代表已经开始扩容了 grow(minCapacity);} 过程分析 当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此时为10。此时,minCapacity - elementData.length > 0成立,所以会进入 grow(minCapacity) 方法。 当add第2个元素时,minCapacity 为2,此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,minCapacity - elementData.length > 0不成立,所以不会进入 (执行)grow(minCapacity) 方法。 添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。 grow()方法123456789101112131415161718192021222324/** * 要分配的最大数组大小 */private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/*** ArrayList扩容的核心方法。*/private void grow(int minCapacity) { // oldCapacity为旧容量,newCapacity为新容量 int oldCapacity = elementData.length; //将oldCapacity 右移一位,其效果相当于oldCapacity /2, //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1); //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量, if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE, //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity);//复制数组} int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!(JDK1.6版本以后)1.6之前是1.5倍+1,右移为除以2的操作 hugeCapacity() 方法:如果minCapacity大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8 ArrayList在使用过程中大量使用了System.arraycopy() 和 Arrays.copyOf()两个方法 如在指定位置添加元素的方法 12345678910111213141516/** * 在此列表中的指定位置插入指定的元素。 *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 */public void add(int index, E element) { rangeCheckForAdd(index); //边界检查 ensureCapacityInternal(size + 1); // Increments modCount!! //arraycopy()方法实现数组自己复制自己 //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量; System.arraycopy(elementData, index, elementData, index + 1, size - index); //先复制再添加元素 elementData[index] = element; size++;} 还有toArray()方法 1234567/** 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。 */public Object[] toArray() { //elementData:要复制的数组;size:要复制的长度 return Arrays.copyOf(elementData, size);} 联系与区别:看两者源代码可以发现 copyOf() 内部实际调用了 System.arraycopy() 方法 arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf() 是系统自动在内部新建一个数组,并返回该数组。 ensureCapacity方法:可以确保数组容纳指定数量的元素,最好在 add 大量元素之前用 ensureCapacity 方法,以减少增量重新分配的次数 LinkedList源码分析内部类 Node结构1234567891011private static class Node<E> { E item;//节点值 Node<E> next;//后继节点 Node<E> prev;//前驱节点 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; }} 构造方法12345678//空构造方法public LinkedList() {}//用已有集合创建链表的方法public LinkedList(Collection<? extends E> c) { this(); addAll(c);} 添加元素12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182public boolean add(E e) { linkLast(e);//这里就只调用了这一个方法 return true;}/** * 链接使e作为最后一个元素。 */void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode;//新建节点 if (l == null) first = newNode; else l.next = newNode;//指向后继元素也就是指向下一个元素 size++; modCount++;}public void add(int index, E element) { checkPositionIndex(index); //检查索引是否处于[0-size]之间 if (index == size)//添加在链表尾部 linkLast(element); else//添加在链表中间 linkBefore(element, node(index));}//addAll(Collection c ):将集合插入到链表尾部public boolean addAll(Collection<? extends E> c) { return addAll(size, c); }//addAll(int index, Collection c): 将集合从指定位置开始插入public boolean addAll(int index, Collection<? extends E> c) { //1:检查index范围是否在size之内 checkPositionIndex(index); //2:toArray()方法把集合的数据存到对象数组中 Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) return false; //3:得到插入位置的前驱节点和后继节点 Node<E> pred, succ; //如果插入位置为尾部,前驱节点为last,后继节点为null if (index == size) { succ = null; pred = last; } //否则,调用node()方法得到后继节点,再得到前驱节点 else { succ = node(index); pred = succ.prev; } // 4:遍历数据将数据插入 for (Object o : a) { @SuppressWarnings(\"unchecked\") E e = (E) o; //创建新节点 Node<E> newNode = new Node<>(pred, e, null); //如果插入位置在链表头部 if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; } //如果插入位置在尾部,重置last节点 if (succ == null) { last = pred; } //否则,将插入的链表与先前链表连接起来 else { pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true;} addAll方法步骤: 检查index范围是否在size之内 toArray()方法把集合的数据存到对象数组中 得到插入位置的前驱和后继节点 遍历数据,将数据插入到指定位置 addFirst(E e)123456789101112131415161718192021public void addFirst(E e) { linkFirst(e);}private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f);//新建节点,以头节点为后继节点 first = newNode; //如果链表为空,last节点也指向该节点 if (f == null) last = newNode; //否则,将头节点的前驱指针指向新节点,也就是指向前一个元素 else f.prev = newNode; size++; modCount++;}//添加元素到最后public void addLast(E e) { linkLast(e); } 获取数据12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public E get(int index) { //检查index范围是否在size之内 checkElementIndex(index); //调用Node(index)去找到index对应的node然后返回它的值 return node(index).item;}//根据对象得到索引的方法public int indexOf(Object o) { int index = 0; if (o == null) { //从头遍历 for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { //从头遍历 for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1;}public int lastIndexOf(Object o) { int index = size; if (o == null) { //从尾遍历 for (Node<E> x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; } } else { //从尾遍历 for (Node<E> x = last; x != null; x = x.prev) { index--; if (o.equals(x.item)) return index; } } return -1;}//是否包含某个对象public boolean contains(Object o) { return indexOf(o) != -1;} 删除方法1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586public E pop() { return removeFirst();}public E remove() { return removeFirst();}public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f);}public E removeLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l);}public E pollLast() { final Node<E> l = last; return (l == null) ? null : unlinkLast(l);}public boolean remove(Object o) { //如果删除对象为null if (o == null) { //从头开始遍历 for (Node<E> x = first; x != null; x = x.next) { //找到元素 if (x.item == null) { //从链表中移除找到的元素 unlink(x); return true; } } } else { //从头开始遍历 for (Node<E> x = first; x != null; x = x.next) { //找到元素 if (o.equals(x.item)) { //从链表中移除找到的元素 unlink(x); return true; } } } return false;}//unlink()方法E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next;//得到后继节点 final Node<E> prev = x.prev;//得到前驱节点 //删除前驱指针 if (prev == null) { first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点 } else { prev.next = next;//将前驱节点的后继节点指向后继节点 x.prev = null; } //删除后继指针 if (next == null) { last = prev;//如果删除的节点是尾节点,令尾节点指向该节点的前驱节点 } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element;}remove(int index):删除指定位置的元素public E remove(int index) { //检查index范围 checkElementIndex(index); //将节点删除 return unlink(node(index)); } CopyOnWriteArrayList都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,我并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。写完之后,再将修改完的副本替换原来的数据,这样就可以保证写操作不会影响读操作了。 对原来的数组对象采用volatile修饰,保证每次读都是最新的 修改的数组的时候,在添加时加锁,保证同时刻只有一个被copy","categories":[],"tags":[{"name":"java","slug":"java","permalink":"http://renjiahui.cn/tags/java/"}]},{"title":"ER图学习","slug":"其他/ER图学习","date":"2021-01-12T03:32:32.000Z","updated":"2021-02-18T09:59:48.880Z","comments":true,"path":"2021/01/12/其他/ER图学习/","link":"","permalink":"http://renjiahui.cn/2021/01/12/%E5%85%B6%E4%BB%96/ER%E5%9B%BE%E5%AD%A6%E4%B9%A0/","excerpt":"","text":"简介ER图,全名实体(Entity)关系(relation)关系,主要用于数据库设计的结构图,表示系统内的主要实体及其属性以及实体之间的关系。 主要组成 实体,可以理解类 属性,实体具有的特性,类的字段 关系,类之间的关系 主要画法","categories":[],"tags":[{"name":"开发工具","slug":"开发工具","permalink":"http://renjiahui.cn/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"}]},{"title":"zookeeper学习笔记","slug":"中间件/zookeeper","date":"2020-12-29T16:00:00.000Z","updated":"2021-01-11T19:11:00.324Z","comments":true,"path":"2020/12/30/中间件/zookeeper/","link":"","permalink":"http://renjiahui.cn/2020/12/30/%E4%B8%AD%E9%97%B4%E4%BB%B6/zookeeper/","excerpt":"","text":"ZooKeeper是一个开源的分布式服务框架,为分布式应用提供协调服务,用来解决分布式应用中的数据管理问题,如:配置管理、域名服务、分布式同步、集群管理等 zookeeper应用场景配置管理在开发过程中需要获取一些配置,如果这些配置发生了改变的话,那么需要每一台的服务都去修改配置,针对这个问题,我们将一些公共的配置放在zookeeper的节点中,然后应用程序连接到zookeeper上,并监听配置的修改,这样就可以实现配置的管理。 分布式锁思路 首先zookeeper中我们可以创建一个/distributed_lock持久化节点 然后再在/distributed_lock节点下创建自己的临时顺序节点,比如:/distributed_lock/task_00000000008 获取所有的/distributed_lock下的所有子节点,并排序 判读自己创建的节点是否最小值(第一位) 如果是,则获取得到锁,执行自己的业务逻辑,最后删除这个临时节点。 如果不是最小值,则需要监听自己创建节点前一位节点的数据变化,并阻塞。 当前一位节点被删除时,我们需要通过递归来判断自己创建的节点是否在是最小的,如果是则执行5); 如果不是则执行6)(就是递归循环的判断) 集群管理集群管理的主要内容包括:节点(机器)增删及Master选取。 节点增删:所有机器约定在父目录GroupMembers下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。新机器加入 也是类似,所有机器收到通知:新兄弟目录加入,highcount又有了。 Master选取:所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。 zookeeper组成 文件系统 通知机制 文件系统 ZooKeeper维护一个类似Linux文件系统的数据结构,用于存储数据 数据模型结构是一种树形结构,由许多节点构成 每个节点叫做ZNode(ZooKeeper Node) 每个节点对应一个唯一路径,通过该路径来标识节点,如 /app1/p_2 每个节点只能存储大约1M的数据 节点类型 持久化目录节点 persistent 客户端与服务器断开连接,该节点仍然存在 持久化顺序编号目录节点 persistent_sequential 客户端与服务器断开连接,该节点仍然存在,此时节点会被顺序编号,如:000001、000002… 临时目录节点 ephemeral 客户端与服务器断开连接,该节点会被删除 临时顺序编号目录节点 ephemeral_sequential 客户端与服务器断开连接,该节点会被删除,此时节点会被顺序编号,如:000001、000002… 通知机制 ZooKeeper是一个基于观察者模式设计的分布式服务管理框架 ZooKeeper负责管理和维护项目的公共数据,并授受观察者的注册(订阅)一旦这些数据发生化,ZooKeeper就会通知已注册的观察者此时观察者就可以做出相应的反应简单来说,客户端注册监听它关心的目录节点,当目录节点发生变化时,ZooKeeper会通知客户端ZooKeeper是一个订阅中心(注册中心) zookeeper集群tips:zookeeper集群,只要有半数以上的机器处于可用状态,那么集群就是可用的。因此一个集群通常是奇数的,因为5台和6台能提供服务的最少机器数都是一样的。 zk角色:leader:负责投票的发起和决议,系统状态的更新,master? leaner: follower:处理来自client的请求,并参与投票 observer:仅处理客户端的连接和请求,同时不参与投票,为了扩展系统,提高读取的速度和并发的能力。 client: 客户端,请求发起方。 集群的数据一致性协议:ZAB协议(Zookeeper Atomic Broadcast(Zookeeper 原子广播协议)) 选举过程服务器初始化启动。(1) 每个Server发出一个投票。由于是初始情况,Server1和Server2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,此时Server1的投票为(1, 0),Server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。 (2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。 (3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下 · 优先检查ZXID。ZXID比较大的服务器优先作为Leader。 · 如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。 对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较两者的ZXID,均为0,再比较myid,此时Server2的myid最大,于是更新自己的投票为(2, 0),然后重新投票,对于Server2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。 (4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1、Server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了Leader。 (5) 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。 服务器运行期间无法和Leader保持连接。(1) 变更状态。Leader挂后,余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。 (2) 每个Server会发出一个投票。在运行期间,每个服务器上的ZXID可能不同,此时假定Server1的ZXID为123,Server3的ZXID为122;在第一轮投票中,Server1和Server3都会投自己,产生投票(1, 123),(3, 122),然后各自将投票发送给集群中所有机器。 (3) 接收来自各个服务器的投票。与启动时过程相同。 (4) 处理投票。与启动时过程相同,此时,Server1将会成为Leader。 (5) 统计投票。与启动时过程相同。 (6) 改变服务器的状态。与启动时过程相同。 数据同步过程选完 leader 以后,zk 就进入状态同步过程。1、leader 等待 server 连接;2、follower 连接 leader,将最大的 zxid 发送给 leader;3、leader 根据 follower 的 zxid 确定同步点;4、完成同步后通知 follower 已经成为 uptodate 状态;5、follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了。 zookeeper 写数据流程1.Client向Zookeeper的Server1上写数据,发送一个写请求 2.如果Server1不是Leader,那么Server1会把接收到的请求进一步转发给Leader,这个Leader会把写请求广播给各个Leader,各个Server写成功后就会通知Leader 3.当Leader收到大多数Server数据写成功了,那么就说明数据写成功了,比如这里有三个节点,只有两个节点数据写成功了,就认为数据写成功了,写成功以后,Leader会告诉Server数据写成功了 4.Server1会通知Client数据写成功了,这时就认为整个写操作成功 zookeeper协议待补充。。。。","categories":[],"tags":[{"name":"zookeeper 分布式","slug":"zookeeper-分布式","permalink":"http://renjiahui.cn/tags/zookeeper-%E5%88%86%E5%B8%83%E5%BC%8F/"}]},{"title":"一些有价值的站点","slug":"其他/Userful URL","date":"2020-11-29T16:00:00.000Z","updated":"2021-01-11T19:10:46.820Z","comments":true,"path":"2020/11/30/其他/Userful URL/","link":"","permalink":"http://renjiahui.cn/2020/11/30/%E5%85%B6%E4%BB%96/Userful%20URL/","excerpt":"","text":"Sonar wiki https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java java language specification https://docs.oracle.com/javase/specs/jls/se8/html/index.html","categories":[],"tags":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://renjiahui.cn/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}]},{"title":"hiveSQL","slug":"大数据/hivesql","date":"2020-10-02T16:00:00.000Z","updated":"2021-01-11T19:41:04.456Z","comments":true,"path":"2020/10/03/大数据/hivesql/","link":"","permalink":"http://renjiahui.cn/2020/10/03/%E5%A4%A7%E6%95%B0%E6%8D%AE/hivesql/","excerpt":"","text":"hive SQL文件格式AvroSerDe ORC Files Parquet 压缩文件 在某些情况下,保持在 Hive 表中压缩数据可以提供比未压缩存储更好的 performance;在磁盘使用和查询 performance 方面。 DDL 建表语句 1CREATE TABLE pokes (foo INT, bar STRING); 创建名为poke的表,包含两列 整数型foo和字符型的bar 1CREATE TABLE invites (foo INT, bar STRING) PARTITIONED BY (ds STRING); 创建名为invites的表,包含两列 整数型foo和字符型的bar,同时建立一个虚拟的分区字段ds,但是该字段不存放实际的数据内容,仅仅是分区的表示。 Hive 中,表中的一个 Partition 对应于表下的一个目录,所有的 Partition 的数据都存储在最字集的目录中。 12345678910CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name [(col_name data_type [COMMENT col_comment], ...)] [COMMENT table_comment] [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)] [CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS] [ROW FORMAT row_format] [STORED AS file_format] [LOCATION hdfs_path] 2.修改表结构 1234hive> ALTER TABLE events RENAME TO 3koobecaf;hive> ALTER TABLE pokes ADD COLUMNS (new_col INT);hive> ALTER TABLE invites ADD COLUMNS (new_col2 INT COMMENT 'a comment');hive> ALTER TABLE invites REPLACE COLUMNS (foo INT, bar STRING, baz INT COMMENT 'baz replaces new_col2'); 可以重命名表,添加列 重命名列或者修改列的数据结构,也可以用来删掉某一行 1ALTER TABLE invites REPLACE COLUMNS (foo INT COMMENT 'only keep the first column'); 删除数据库 1DROP TABLE pokes; DML LOAD 转载数据到hive表中 12LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)] [INPUTFORMAT 'inputformat' SERDE 'serde'] (``3.0` `or later) INSERT UPDATE DELETE MERGE","categories":[],"tags":[{"name":"大数据","slug":"大数据","permalink":"http://renjiahui.cn/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"}]},{"title":"hadoop","slug":"大数据/hadoop","date":"2020-09-30T16:00:00.000Z","updated":"2021-01-11T18:55:22.521Z","comments":true,"path":"2020/10/01/大数据/hadoop/","link":"","permalink":"http://renjiahui.cn/2020/10/01/%E5%A4%A7%E6%95%B0%E6%8D%AE/hadoop/","excerpt":"","text":"简介hadoop是一个分布式系统的基础架构,广义的hadoop指的是一整个大数据处理的生态系统 hadoop本体主要包括 分布式文件系统HDFS,分布式计算架构mapreduce,分布式调度系统yarn,和一些工具组件。 1.hadoop 1.x和2.x的区别:在1.x时代,mapreduce同时处理资源调度和业务逻辑运算,在2.x版本,hadoop加入了yarn负责资源调度,mapreduce只负责计算。 2.相关组件简介HDFS HDFS是一个主从架构,包含一个namenode和多个datanode。 Namenode是一个中心服务器,负责管理文件系统的名字空间(namespace)以及客户端对文件的访问。集群中的Datanode一般是一个节点一个,负责管理它所在节点上的存储。HDFS暴露了文件系统的名字空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组Datanode上。Namenode执行文件系统的名字空间操作,比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。 HDFS将文件存储为大小一致的数据块,文件的所有数据块都会写入副本,储存在不同节点上,数据块大小和副本数都是可以配置的(默认大小:64MB,默认副本数:3) Namenode全权管理数据块的复制,它周期性地从集群中的每个Datanode接收心跳信号和块状态报告(Blockreport)。接收到心跳信号意味着该Datanode节点工作正常。块状态报告包含了一个该Datanode上所有数据块的列表。 不同节点之间的通信,采用RPC调用 namenode和datanode采用心跳机制确认是否有效,若datanode宕机当值副本数低于设定值,namenode会检测需要进行复制的数据块,启动复制操作。 在进行复制的时候是流水线复制的, 流水线复制 当客户端向HDFS文件写入数据的时候,一开始是写到本地临时文件中。假设该文件的副本系数设置为3,当本地临时文件累积到一个数据块的大小时,客户端会从Namenode获取一个Datanode列表用于存放副本。然后客户端开始向第一个Datanode传输数据,第一个Datanode一小部分一小部分(4 KB)地接收数据,将每一部分写入本地仓库,并同时传输该部分到列表中第二个Datanode节点。第二个Datanode也是这样,一小部分一小部分地接收数据,写入本地仓库,并同时传给第三个Datanode。最后,第三个Datanode接收数据并存储在本地。因此,Datanode能流水线式地从前一个节点接收数据,并在同时转发给下一个节点,数据以流水线的方式从前一个Datanode复制到下一个。 MapReduceHadoop MapReduce是一个软件框架,用于轻松编写应用程序,这些应用程序以可靠,容错的方式并行处理大型硬件集群(数千个节点)上的大量数据(多TB数据集)。 MapReduce作业通常将输入数据集拆分为独立的块,这些任务由地图任务以完全并行的方式进行处理。该框架对地图的输出进行排序,然后将其输入到reduce任务中。通常,作业的输入和输出都存储在文件系统中。该框架负责安排任务,监视任务并重新执行失败的任务。 通常,计算节点和存储节点是相同的,即MapReduce框架和Hadoop分布式文件系统(请参阅HDFS体系结构指南)在同一组节点上运行。此配置使框架可以在已经存在数据的节点上有效地调度任务,从而在整个群集中产生很高的聚合带宽。 MapReduce框架由一个master ResourceManager,每个群集节点一个工作器NodeManager和每个应用程序MRAppMaster组成(请参阅YARN体系结构指南)。 最少地,应用程序通过适当的接口和/或抽象类的实现来指定输入/输出位置和供应图,并减少功能。这些以及其他作业参数构成作业配置。 然后,Hadoop作业客户端将作业(jar /可执行文件等)和配置提交给ResourceManager,然后由ResourceManager负责将软件/配置分发给工作人员,安排任务并对其进行监视,为工作提供状态和诊断信息,客户。 YARN YARN 的组件: ResourceManager 全局的资源管理器 ApplicationMaster 单个应用程序的管理 NodeManager 每个节点上的资源和任务管理器 Container 每一个节点上的资源的封装 一个分布式应用程序代替一个 MapReduce 作业 在 YARN 架构中,一个全局 ResourceManager 以主要后台进程的形式运行,它通常在专用机器上运行,在各种竞争的应用程序之间仲裁可用的集群资源。ResourceManager 会追踪集群中有多少可用的活动节点和资源,协调用户提交的哪些应用程序应该在何时获取这些资源。ResourceManager 是惟一拥有此信息的进程,所以它可通过某种共享的、安全的、多租户的方式制定分配(或者调度)决策(例如,依据应用程序优先级、队列容量、ACLs、数据位置等)。 在用户提交一个应用程序时,一个称为 ApplicationMaster 的轻量型进程实例会启动来协调应用程序内的所有任务的执行。这包括监视任务,重新启动失败的任务,推测性地运行缓慢的任务,以及计算应用程序计数器值的总和。这些职责以前分配给所有作业的单个 JobTracker。ApplicationMaster 和属于它的应用程序的任务,在受 NodeManager 控制的资源容器中运行。 NodeManager 是 TaskTracker 的一种更加普通和高效的版本。没有固定数量的 map 和 reduce slots,NodeManager 拥有许多动态创建的资源容器。容器的大小取决于它所包含的资源量,比如内存、CPU、磁盘和网络 IO。目前,仅支持内存和 CPU (YARN-3)。未来可使用 cgroups 来控制磁盘和网络 IO。一个节点上的容器数量,由配置参数与专用于从属后台进程和操作系统的资源以外的节点资源总量(比如总 CPU 数和总内存)共同决定。 用户向yarn提交任务后的流程: 用户向YARN中提交应用程序,其中包括ApplicationMaster程序、启动ApplicationMaster的命令、用户程序等。 ResourceManager为该应用程序分配第一个Container,并与对应的NodeManager通信,要求它在这个Container中启动应用程序的ApplicationMaster。 ApplicationMaster首先向ResourceManager注册,这样用户就可以直接通过ResourceManager查看应用程序的运行状态,然后它将为各个任务申请资源,并监控它的运行状态,直到运行结束,即重复步骤4~7。 ApplicationMaster采用轮询的方式通过RPC协议向ResourceManager申请和领取资源。 一旦ApplicationMaster申请到资源后,便与对应的NodeManager通信,要求它启动任务。 NodeManager为任务设置好运行环境(包括环境变量、JAR包、二进制程序等)后,将任务启动命令写到一个脚本中,并通过运行该脚本启动任务。 各个任务通过某个RPC协议向ApplicationMaster汇报自己的状态和进度,以让ApplicationMaster随时掌握各个任务的运行状态,从而可以在任务失败时重新启动任务。 应用程序运行完成后,ApplicationMaster向ResourceManager注销并关闭自己。 3.hadoop 三大发行版Apache 社区版,企业实际使用并不多。最原始(基础)版本。这是学习hadoop的基础。 cloudera CDH是Cloudera的Hadoop发行版,完全开源,比Apache Hadoop在兼容性,安全 性,稳定性上有所增强。 Hortonworks Hortonworks 的主打产品是Hortonworks Data Platform (HDP),也同样是100%开 源的产品,HDP除常见的项目外还包含了Ambari,一款开源的安装和管理系统 4.hadoop生态 主要组件: 1)HDFS一个提供高可用的获取应用数据的分布式文件系统。2)MapReduce一个并行处理大数据集的编程模型。3)HBase一个可扩展的分布式数据库,支持大表的结构化数据存储。是一个建立在 HDFS 之上的,面向列的 NoSQL 数据库,用于快速读/写大量数据。4)Hive一个建立在 Hadoop 上的数据仓库基础构架。它提供了一系列的工具;可以用来进行数据提取转化加载(ETL),这是一种可以存储、查询和分析存储在 Hadoop 中的大规模数据的机制。Hive 定义了简单的类 SQL 查询语言,称为 HQL,它允许不熟悉 MapReduce 的开发人员也能编写数据查询语句,然后这些语句被翻译为 Hadoop 上面的 MapReduce 任务。5)Mahout可扩展的机器学习和数据挖掘库。它提供的 MapReduce 包含很多实现方法,包括聚类算法、回归测试、统计建模。6)Pig一个支持并行计算的高级的数据流语言和执行框架。它是 MapReduce 编程的复杂性的抽象。Pig 平台包括运行环境和用于分析 Hadoop 数据集的脚本语言(PigLatin)。其编译器将 PigLatin 翻译成 MapReduce 程序序列。7)Zookeeper—个应用于分布式应用的高性能的协调服务。它是一个为分布式应用提供一致性服务的软件,提供的功能包括配置维护、域名服务、分布式同步、组服务等。8)Ambari一个基于 Web 的工具,用来供应、管理和监测 Hadoop 集群,包括支持 HDFS、MapReduceAHive、HCatalog、HBase、ZooKeeperAOozie、Pig 和 Sqoop 。Ambari 也提供了一个可视的仪表盘来查看集群的健康状态,并且能够使用户可视化地查看 MapReduce、Pig 和 Hive 应用来诊断其性能特征。Hadoop 的生态圈还包括以下几个框架,用来与其他企业融合。1)Sqoop一个连接工具,用于在关系数据库、数据仓库和 Hadoop 之间转移数据。Sqoop 利用数据库技术描述架构,进行数据的导入/导出;利用 MapReduce 实现并行化运行和容错技术。2)Flume提供了分布式、可靠、高效的服务,用于收集、汇总大数据,并将单台计算机的大量数据转移到 HDFS。它基于一个简单而灵活的架构,并提供了数据流的流。它利用简单的可扩展的数据模型,将企业中多台计算机上的数据转移到 Hadoop。 HbaseRow key 行键 字典序排序 Columns Family 列族 Cell 相当于一行 TimeStamp HDFS 备份机制 zookeeper 访问机制 参考:hadoop官网文档 ,IBM","categories":[],"tags":[{"name":"大数据","slug":"大数据","permalink":"http://renjiahui.cn/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"}]},{"title":"分布式事务","slug":"java/分布式事务","date":"2020-09-19T16:00:00.000Z","updated":"2021-01-11T19:23:18.300Z","comments":true,"path":"2020/09/20/java/分布式事务/","link":"","permalink":"http://renjiahui.cn/2020/09/20/java/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/","excerpt":"","text":"什么是分布式事务分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。 简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。 本质上来说,分布式事务就是为了保证不同数据库的数据一致性。 CAP理论在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance)3 个要素最多只能同时满足两个,不可兼得。其中,分区容忍性又是不可或缺的。 一致性原则弱一致性:写入一个新值后,可以读的出来也可以读不出来,对数据完整性要求不高的场景。 最终一致性:写入一个新值后,在某个时间窗口可以读出来(消息中间件场景) 强一致性:新数据写入后,一定是一致的。 Base理论核心思想: 基本可用(Basically Available):指分布式系统在出现故障时,允许损失部分的可用性来保证核心可用。 软状态(Soft State):指允许分布式系统存在中间状态,该中间状态不会影响到系统的整体可用性。 最终一致性(Eventual Consistency):指分布式系统中的所有副本数据经过一定时间后,最终能够达到一致的状态。 XA协议待补充。。。 TCC模型三个阶段,分别是try,commit,cancel 需要原来的服务调用接口支持三个逻辑:分布式事务的管理,由事务管理器处理 然后你原本的一个接口,要改造为3个逻辑,Try-Confirm-Cancel。 先是服务调用链路依次执行Try逻辑,完成业务检查,准备业务资源 如果都正常的话,TCC分布式事务框架推进执行Confirm逻辑,完成整个事务,执行业务逻辑,try中预留了资源,Confirm可以成功 如果某个服务的Try逻辑有问题,TCC分布式事务框架感知到之后就会推进执行各个服务的Cancel逻辑,撤销之前执行的各种操作,释放 执行过程中挂掉,可以设置超时,然后通过日志重新执行。 全局ID生成器snowflake是twitter开源的分布式ID生成算法,其核心思想为,一个long型的ID: 41bit作为毫秒数 10bit作为机器编号 12bit作为毫秒内序列号","categories":[],"tags":[{"name":"分布式","slug":"分布式","permalink":"http://renjiahui.cn/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"}]},{"title":"日志系统","slug":"java/日志系统","date":"2020-08-28T16:00:00.000Z","updated":"2021-01-11T19:23:06.072Z","comments":true,"path":"2020/08/29/java/日志系统/","link":"","permalink":"http://renjiahui.cn/2020/08/29/java/%E6%97%A5%E5%BF%97%E7%B3%BB%E7%BB%9F/","excerpt":"","text":"JAVA日志框架日志门面框架和日志实现框架的区别目前的日志框架有JDK自带的logging,log4j1、log4j2、logback ,这些框架都自己定制了日志 API ,并且有相应的实现;目前用于实现日志统一的框架 Apache commons-logging、slf4j ,遵循面向接口编程的原则,这两大框架可以让用户在程序运行期间去选择具体的日志实现系统(log4j1\\log4j2\\logback等)来记录日志,是统一抽象出来的一些接口。 日志级别 FATAL — 表示需要立即被处理的系统级错误。 ERROR — 该级别的错误也需要马上被处理,但是紧急程度要低于FATAL级别。当ERROR错误发生时,已经影响了用户的正常访问。 WARN — 该日志表示系统可能出现问题,也可能没有,这种情况如网络的波动等。 INFO — 该种日志记录系统的正常运行状态,例如某个子系统的初始化,某个请求的成功执行等等。 DEBUG or TRACE — 这两种日志具体的规范应该由项目组自己定义,该级别日志的主要作用是对系统每一步的运行状态进行精确的记录。 java原生日志系统JUL工作原理","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"redis study","slug":"中间件/redis","date":"2020-07-29T16:00:00.000Z","updated":"2021-01-11T17:56:07.616Z","comments":true,"path":"2020/07/30/中间件/redis/","link":"","permalink":"http://renjiahui.cn/2020/07/30/%E4%B8%AD%E9%97%B4%E4%BB%B6/redis/","excerpt":"","text":"redis studyredis的一些笔记(个人向) 详细文档参见 http://redisdoc.com/string/index.html redis原理单线程,用队列处理并发访问,由于是内存型数据库 用来当作缓存,读写比较快。单机10左右tps 与memcache比较 redis的优势:可以持久化,数据类型多样,支持事务 redis 数据类型 string 常用命令: set,get,decr,incr,mget 等。 string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB 1SET key value redis string 二进制安全,可以存放任何数据 主要由SDS结构体实现,而不是直接用 \\0表示字符串结尾, 12345struct sdshdr {int len;//记录buf数组大小int free;//记录buf数组还有多少可用空间char buf[];//字符串实体,保存字符串的内容}; hash 常用命令: hget,hset,hgetall 等。 Redis hash 是一个键值(key=>value)对集合。 Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。 1HSET key field value list 常用命令: lpush,rpush,lpop,rpop,lrange等 底层实现是一个链表 Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。 1lpush key value set 常用命令: sadd,spop,smembers,sunion 等 Redis的Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 1sadd key member zset(有序集合) 常用命令: zadd,zrange,zrem,zcard等 Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。 1zadd key score member Redis 过期时间在设置redis时可以设置过期时间 删除方式:定期删除+惰性删除 定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载! 惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈! 内存淘汰机制由于删除方式的原因,容易导致大量过期的key堆积未被删除,容易导致过期key堆积在内存中,使得内存不足,或超出上限。 这时候需要有内存淘汰机制来及时清理内存。 noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错。 allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。 allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机。 volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。 volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。 volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。 4.0版本后增加以下两种: volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰 allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key valatile 过期数据 allkeys 所有数据 lru 最少使用 lfu 最不经常使用 redis 持久化 快照(snapshotting)持久化(RDB) redis 默认持久化方式 12345save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 只追加文件(append-only file,AOF) 与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启: 1appendonly yes 开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。 在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是: 123appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘appendfsync no #让操作系统决定何时进行同步 为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。 redis 事务 单线程和事务关系的思考:(为什么redis单线程还需要事务) 单线程只能保证单个命令是原子的,如果需要多个命令是原子性的,需要提供事务来实现。 MULTI 开启事务 EXEC 提交事务 DISCARD 丢弃事务 WATCH watch 为redis提供了CAS 功能,在进行操作前监视那些键值。 缓存雪崩和缓存穿透缓存雪崩:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。 设定随机失效时间 缓存穿透缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。 解决方案:有很多种方法可以有效地解决缓存穿透问题,最常见的是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。 主从复制架构方面的数据一致性写时,先删缓存再写数据库 读时,先读缓存,缓存不存在,再读数据库,然后写缓存。","categories":[],"tags":[{"name":"Java redis","slug":"Java-redis","permalink":"http://renjiahui.cn/tags/Java-redis/"}]},{"title":"rabbitMQ笔记","slug":"中间件/rabbitMQ","date":"2020-05-31T16:00:00.000Z","updated":"2021-01-11T17:56:00.809Z","comments":true,"path":"2020/06/01/中间件/rabbitMQ/","link":"","permalink":"http://renjiahui.cn/2020/06/01/%E4%B8%AD%E9%97%B4%E4%BB%B6/rabbitMQ/","excerpt":"","text":"相关概念使用消息队列的原因 不同进程(process)之间传递消息时,两个进程之间耦合程度过高,改动一个进程,引发必须修改另一个进程,为了隔离这两个进程,在两进程间抽离出一层(一个模块),所有两进程之间传递的消息,都必须通过消息队列来传递,单独修改某一个进程,不会影响另一个; 不同进程(process)之间传递消息时,为了实现标准化,将消息的格式规范化了,并且,某一个进程接受的消息太多,一下子无法处理完,并且也有先后顺序,必须对收到的消息进行排队,因此诞生了事实上的消息队列; rabbitMQ是使用erlang开发的一种基于AMQP协议的消息系统 Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输, Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。 Queue:消息的载体,每个消息都会被投到一个或多个队列。 Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来. Routing Key:路由关键字,exchange根据这个关键字进行消息投递。 vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离。 Producer:消息生产者,就是投递消息的程序. Consumer:消息消费者,就是接受消息的程序. Channel:消息通道,在客户端的每个连接里,可建立多个channel. RabbitMQ队列模型 简单队列: 一个生产者对应一个消费者 work模式: 简单队列只要消息从队列中获取,无论消费者获取到消息后是否成功消费,比如遇到状况:断电,都认为是消息已经成功消费;work模式消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者反馈,如果消费这一直没有反馈,则该消息一直处于不可用状态。channel.basicQos(1); 能者多劳,否则平均分配 消息分发机制简单队列一个生产者对应一个消费者,简单队列只要消息从队列中获取,无论消费者获取到消息后是否成功消费,比如遇到状况:断电,都认为是消息已经成功消费。 work模式work模式:一个队列对应多个消费者 分发方式: 公平方式 channel.basicQos(1); 保证一个消费者同一时刻只处理一个消息 轮询方式 将第n个消息发给n取余后的消费者,消费能力不同时会阻塞。 消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者反馈,如果消费这一直没有反馈,则该消息一直处于不可用状态。channel.basicQos(1); 能者多劳,否则平均分配 发布订阅模式一个消费者将消息首先发送到交换器,交换器绑定到多个队列,然后被监听该队列的消费者所接收并消费。 交换器类型 fanout fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。生产者发送到Exchange的所有消息都会路由到绑定的Queue,并最终被两个消费者消费。 direct direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。(在实际使用RabbitMQ的过程中并没有binding key这个参数,只有routing key,为了区分我们把交换机和队列绑定时传的参数叫binding key,把发送消息时带的这个参数叫routing key) topic 前面讲到direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但direct是完全匹配,而通过topic可以进行模糊匹配 消息可靠性和分布式可靠性机制不同角色丢失消息: 生产者:(推荐)可以开启confirm模式,在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉你说这个消息ok了。如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。 mq:设置mq的消息持久化,在消息写入后就进行持久化,可以设置交换器,队列和消息的持久化,开启了持久化的交换器只能与对应持久的队列绑定 消费者:关闭自动ack功能,自动ack只要消费者不抛异常则认为消费,存在的情况:可能接受消息后直接挂了,也认为被消费,可以采用手动ack来实现,代码处理完之后再进行ack,确保消息被正确消费。(存在重复消费的问题,需要在代码中实现消息的幂等性)","categories":[],"tags":[{"name":"中间件 MQ","slug":"中间件-MQ","permalink":"http://renjiahui.cn/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6-MQ/"}]},{"title":"rocketMQ","slug":"中间件/rocketMQ","date":"2020-05-29T16:00:00.000Z","updated":"2021-01-11T17:56:18.049Z","comments":true,"path":"2020/05/30/中间件/rocketMQ/","link":"","permalink":"http://renjiahui.cn/2020/05/30/%E4%B8%AD%E9%97%B4%E4%BB%B6/rocketMQ/","excerpt":"","text":"架构示意图 组件介绍:NameServer: 主要负责对于源数据的管理,包括了对于Topic和路由信息的管理。 NameServer是一个功能齐全的服务器,其角色类似Dubbo中的Zookeeper,但NameServer与Zookeeper相比更轻量。主要是因为每个NameServer节点互相之间是独立的,没有任何信息交互。 NameServer压力不会太大,平时主要开销是在维持心跳和提供Topic-Broker的关系数据。 但有一点需要注意,Broker向NameServer发心跳时, 会带上当前自己所负责的所有Topic信息,如果Topic个数太多(万级别),会导致一次心跳中,就Topic的数据就几十M,网络情况差的话, 网络传输失败,心跳失败,导致NameServer误认为Broker心跳失败。 NameServer 被设计成几乎无状态的,可以横向扩展,节点之间相互之间无通信,通过部署多台机器来标记自己是一个伪集群。 每个 Broker 在启动的时候会到 NameServer 注册,Producer 在发送消息前会根据 Topic 到 NameServer 获取到 Broker 的路由信息,Consumer 也会定时获取 Topic 的路由信息。 所以从功能上看NameServer应该是和 ZooKeeper 差不多,据说 RocketMQ 的早期版本确实是使用的 ZooKeeper ,后来改为了自己实现的 NameServer 。 Broker 消息中转角色,负责存储消息,转发消息。 Broker是具体提供业务的服务器,单个Broker节点与所有的NameServer节点保持长连接及心跳,并会定时将Topic信息注册到NameServer,顺带一提底层的通信和连接都是基于Netty实现的。 Broker负责消息存储,以Topic为纬度支持轻量级的队列,单机可以支撑上万队列规模,支持消息推拉模型。 官网上有数据显示:具有上亿级消息堆积能力,同时可严格保证消息的有序性。 Producer 消息生产者,负责产生消息,一般由业务系统负责产生消息。 Producer由用户进行分布式部署,消息由Producer通过多种负载均衡模式发送到Broker集群,发送低延时,支持快速失败。 RocketMQ 提供了三种方式发送消息:同步、异步和单向 同步发送:同步发送指消息发送方发出数据后会在收到接收方发回响应之后才发下一个数据包。一般用于重要通知消息,例如重要通知邮件、营销短信。 异步发送:异步发送指发送方发出数据后,不等接收方发回响应,接着发送下个数据包,一般用于可能链路耗时较长而对响应时间敏感的业务场景,例如用户视频上传后通知启动转码服务。 单向发送:单向发送是指只负责发送消息而不等待服务器回应且没有回调函数触发,适用于某些耗时非常短但对可靠性要求并不高的场景,例如日志收集。 Consumer 消息消费者,负责消费消息,一般是后台系统负责异步消费。 Consumer也由用户部署,支持PUSH和PULL两种消费模式,支持集群消费和广播消息,提供实时的消息订阅机制。 Pull:拉取型消费者(Pull Consumer)主动从消息服务器拉取信息,只要批量拉取到消息,用户应用就会启动消费过程,所以 Pull 称为主动消费型。 Push:推送型消费者(Push Consumer)封装了消息的拉取、消费进度和其他的内部维护工作,将消息到达时执行的回调接口留给用户应用程序来实现。所以 Push 称为被动消费类型,但从实现上看还是从消息服务器中拉取消息,不同于 Pull 的是 Push 首先要注册消费监听器,当监听器处触发后才开始消费消费。 名词概念 Topic 消息主题,消息的逻辑分类,可以理解为抽象的消息类型,所有的操作都是基于topic进行的 Tag 标签,topic的细化,对同一个topic里面的消息进行更进一步的分类,方便消费者进行过滤,如压测数据会添加对应的压测的标签 Message 消息,表示消息的主要内容,在rocketMQ中每条消息主要包括下面内容: Message Id 消息的全局唯一标识,一般在发送过程中由rocketMQ自动生成 Message Key 消息的业务标识,由生产者设置,标识业务逻辑 疑问点: tag和messageKey的区别 message body 消息的主要内容,需要进行消息序列化 Messagequeue 消息队列,在实际消息承载的组件是由消息队列承担的,默认一个topic 会分配4个queue,分布在不同的broker上 group 分组信息,可以分为Producer Group和Consumer Group,一个topic可以有多个group。 集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。 广播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。 offset 偏移量,由于采用的是队列的形式,消费消息完成后,需要一个标识来表示消费完成的位置,consumer可以根据offset继续消费。 ordered message 消息有序指的是一类消息消费时,能按照发送的顺序来消费。例如:一个订单产生了三条消息分别是订单创建、订单付款、订单完成。消费时要按照这个顺序消费才能有意义,但是同时订单之间是可以并行消费的。RocketMQ可以严格的保证消息有序。 顺序消息分为全局顺序消息与分区顺序消息,全局顺序是指某个Topic下的所有消息都要保证顺序;部分顺序消息只要保证每一组消息被顺序消费即可。 全局顺序 对于指定的一个 Topic,所有消息按照严格的先入先出(FIFO)的顺序进行发布和消费。 适用场景:性能要求不高,所有的消息严格按照 FIFO 原则进行消息发布和消费的场景 分区顺序 对于指定的一个 Topic,所有消息根据 sharding key 进行区块分区。 同一个分区内的消息按照严格的 FIFO 顺序进行发布和消费。 Sharding key 是顺序消息中用来区分不同分区的关键字段,和普通消息的 Key 是完全不同的概念。 适用场景:性能要求高,以 sharding key 作为分区字段,在同一个区块中严格的按照 FIFO 原则进行消息发布和消费的场景。","categories":[],"tags":[{"name":"中间件 MQ","slug":"中间件-MQ","permalink":"http://renjiahui.cn/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6-MQ/"}]},{"title":"Spring源码解析","slug":"框架/spring源码","date":"2020-04-29T16:00:00.000Z","updated":"2021-01-11T19:45:42.190Z","comments":true,"path":"2020/04/30/框架/spring源码/","link":"","permalink":"http://renjiahui.cn/2020/04/30/%E6%A1%86%E6%9E%B6/spring%E6%BA%90%E7%A0%81/","excerpt":"","text":"Spring源码阅读ApplicationContext 相关类图 Resource类图:","categories":[],"tags":[{"name":"框架学习 Java","slug":"框架学习-Java","permalink":"http://renjiahui.cn/tags/%E6%A1%86%E6%9E%B6%E5%AD%A6%E4%B9%A0-Java/"}]},{"title":"设计模式","slug":"其他/设计模式","date":"2020-02-19T16:00:00.000Z","updated":"2021-01-11T19:21:20.262Z","comments":true,"path":"2020/02/20/其他/设计模式/","link":"","permalink":"http://renjiahui.cn/2020/02/20/%E5%85%B6%E4%BB%96/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","excerpt":"","text":"设计模式设计模式的六大原则 开闭原则 对扩展开放,对修改封闭 里氏代换原则 任何基类可以出现的地方,子类一定可以出现。 依赖倒转原则 抽象不应该依赖于细节,细节应该依赖于抽象 接口隔离原则 使用多个专门的接口,而不使用单一的总接口 迪米特法则,又称最少知道原则(Demeter Principle) 一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立 合成复用原则 尽量使用对象组合,而不是继承来达到复用的目的 单一职责原则 一个类只负责一个功能领域中的相应职责 设计模式分类三大类: 创建型模式 单例,工厂(3种),建造者,原型 结构型模式 适配器,桥接,装饰,外观,代理,享元,组合 行为型模式 责任链,命令模式,解释器,迭代器,中介者,备忘录,状态,策略,模板方法,访问者 简单工厂简单工厂模式 是一个工厂对象根据收到的消息决定要创建的实例的类型。 优点:工厂类中包含了必要的逻辑,根据用户需要实例化对应的类。 缺点:容易违反高内聚低耦合的原则,增加一个类需要修改工厂代码,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性和扩展性 12345678910111213141516public class FoodFactory { public static Food makeFood(String name) { if (name.equals(\"noodle\")) { Food noodle = new LanZhouNoodle(); noodle.addSpicy(\"more\"); return noodle; } else if (name.equals(\"chicken\")) { Food chicken = new HuangMenChicken(); chicken.addCondiment(\"potato\"); return chicken; } else { return null; } }} 工厂方法模式定义一个创建对象的工厂接口,让子类决定实例化哪个类,将创建工作推迟到子类中。 具体实现 工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象。 优点:符合开-闭原则(扩展开放,修改封闭),解决了简单工厂存在的问题,增加一个产品的实现时,不需要修改父类工厂类的逻辑,只要增加一个子类的实现即可。缺点:一个具体工厂类只能创建一种具体产品。 1234567891011121314151617181920212223242526272829public interface FoodFactory { Food makeFood(String name);}public class ChineseFoodFactory implements FoodFactory { @Override public Food makeFood(String name) { if (name.equals(\"A\")) { return new ChineseFoodA(); } else if (name.equals(\"B\")) { return new ChineseFoodB(); } else { return null; } }}public class AmericanFoodFactory implements FoodFactory { @Override public Food makeFood(String name) { if (name.equals(\"A\")) { return new AmericanFoodA(); } else if (name.equals(\"B\")) { return new AmericanFoodB(); } else { return null; } }} 抽象工厂模式区别:普通工厂产出是一个产品(实例),抽象工厂产出是一个抽象(接口)。区别在于,若添加一个新的产品,前者是修改工厂,后者是创建新工厂(符合“闭合原则”)。概述:创建其他工厂的工厂。 三者关系,简单工厂是对工厂方法的缩减,将具体实现全部集中到工厂类中,抽象工厂是对工厂方法的增强,可以添加多种产品的实现。减少工厂数量,扩展较不方便。 代码参考github 单例模式只产生一个实例永久驻留,减少资源开销。 使用场景: 确保系统中对应的类只有一个实例存在。 实现: 声明为private来隐藏构造器 private static Singleton实例 声明为public来暴露实例获取方法 实现方式: 懒汉式 使用static 定义静态成员变量或静态代码 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public class Singleton { private static Singleton instance = null; private Singleton() { }; public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }}//使用synchronized保证线程安全public class Singleton { private volatile static Singleton instance; private Singleton() { }; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }} //改进型public class Singleton { private static Singleton instance; private Singleton() { }; public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { instance = new Singleton(); } } return instance; }} //双重判定public class Singleton { private static Singleton instance; private Singleton() { }; public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; }} 饿汉式 12345678910111213141516171819public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() { }; public static Singleton getInstance() { return instance; }} public class Singleton { private Singleton instance = null; //静态代码块 static { instance = new Singleton(); } private Singleton (){} public static Singleton getInstance() { return this.instance; }} Initialization Demand Holder (IoDH)模式 12345678910111213//Initialization on Demand Holder class Singleton { private Singleton() { } private static class HolderClass { private final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return HolderClass.instance; }} 在IoDH中,我们在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用,由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。 原型模式原理:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。 12345678910111213141516171819202122232425262728public class Customer implements Serializable,Cloneable { private static final long serialVersionUID = -8836367807392087516L; private String address; public void setAddress(String address) { this.address = address; } public String getAddress() { return address; } //浅拷贝 @Override protected Customer clone() throws CloneNotSupportedException { return (Customer)super.clone(); } //深拷贝 protected Customer deepClone() throws IOException, ClassNotFoundException { //序列化输出 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(this); //反序列化 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (Customer) objectInputStream.readObject(); }} 建造者模式建造者模式的核心在于如何一步步构建一个包含多个组成部件的完整对象,使用相同的构建过程构建不同的产品,在软件开发中,如果我们需要创建复杂对象并希望系统具备很好的灵活性和可扩展性可以考虑使用建造者模式。 代码参考github 适配器模式可以将一个类的接口和另一个类的接口匹配起来,而无须修改原来的适配者接口和抽象目标类接口。 1234567891011class Adapter extends Target { private Adaptee adaptee; //维持一个对适配者对象的引用 public Adapter(Adaptee adaptee) { this.adaptee=adaptee; } public void request() { adaptee.specificRequest(); //转发调用 } } 适配器继承或依赖已有的对象,实现想要的目标接口。 个人理解:继承老的接口,并重写调用方法,在调用方法中调用新增的接口 桥接模式将抽象部分与实现部分分离,使它们都可以独立的变化。 个人理解:一个抽象类有一个接口类对象,和一个抽象方法,都有各自不同的实现,在使用时将不同的对象和不同方法实现之间解耦 代码参考github 组合模式1、组合模式,就是在一个对象中包含其他对象,这些被包含的对象可能是终点对象(不再包含别的对象),也有可能是非终点对象(其内部还包含其他对象,或叫组对象),我们将对象称为节点,即一个根节点包含许多子节点,这些子节点有的不再包含子节点,而有的仍然包含子节点,以此类推。 2、所谓组合模式,其实说的是对象包含对象的问题,通过组合的方式(在对象内部引用对象)来进行布局,我认为这种组合是区别于继承的,而另一层含义是指树形结构子节点的抽象(将叶子节点与数枝节点抽象为子节点),区别于普通的分别定义叶子节点与数枝节点的方式。 它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。 1234567891011121314151617181920212223242526272829303132333435import java.util.ArrayList;import java.util.List; public class Employee { private String name; private String dept; private int salary; private List<Employee> subordinates; //构造函数 public Employee(String name,String dept, int sal) { this.name = name; this.dept = dept; this.salary = sal; subordinates = new ArrayList<Employee>(); } public void add(Employee e) { subordinates.add(e); } public void remove(Employee e) { subordinates.remove(e); } public List<Employee> getSubordinates(){ return subordinates; } public String toString(){ return (\"Employee :[ Name : \"+ name +\", dept : \"+ dept + \", salary :\" + salary+\" ]\"); } }","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"Java并发","slug":"java/Java多线程","date":"2020-01-22T16:00:00.000Z","updated":"2021-02-18T09:00:35.391Z","comments":true,"path":"2020/01/23/java/Java多线程/","link":"","permalink":"http://renjiahui.cn/2020/01/23/java/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B/","excerpt":"","text":"主要的问题: 什么是多线程 如何实现多线程 多线程的问题及解决方法 1.什么是多线程 进程与线程:进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位) 一个程序至少有一个进程,一个进程至少有一个线程。 进程,是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。它的执行需要系统分配资源创建实体之后,才能进行。 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 通俗一点说,进程就是程序的一次执行,而线程可以理解为进程中的执行的一段程序片段。 区别 1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。 2)通信:进程间通信IPC(Inter-Process Communication),线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。 3)调度和切换:线程上下文切换比进程上下文切换要快得多。 线程的状态转换 wait和sleep的区别sleep的作用是当前执行的线程让出CPU资源,但不释放同步资源,等待CPU重新分配后继续执行。 wait指当前线程暂时让出同步资源锁,只有调用了notify()的线程才可 sleep可以在任意地方调用,wait只能在同步方法和同步块中调用。 sleep是Thread类的方法,wait是object类的方法 并发程序的同步方式进程:无名管道(pipe)、有名管道(FIFO)、信号、共享内存、消息队列、信号量、套接字(socket)线程:互斥量、读写锁、自旋锁、线程信号、条件变量 2.如何实现多线程 继承Thread 12345678910111213141516171819class thread extends Thread{ String name; public thread(String name) { this.name = name; } public void run() { System.out.println(this.name); }}public class ThreadTest { public static void main(String[] args) { thread thread1 = new thread(\"1\"); thread thread2 = new thread(\"2\"); thread1.start(); thread2.start(); System.out.println(\"main\"); }} 实现runnable接口 123456789101112131415161718class thread implements Runnable{ String name; public thread(String name) { this.name = name; } @Override public void run() { System.out.println(this.name); }}public class ThreadTest { public static void main(String[] args) { new Thread(new thread(\"1\")).start(); new Thread(new thread(\"2\")).start(); System.out.println(\"main\"); }} 运行时区别,要将runnable对象传入一个Thread对象的构造函数内 实现Callable接口和Future,FutureTask 可以在线程执行完毕后获取返回的结果。 Future 是线程池提交了callable任务后的返回的对象 采用Future实现多线程: 12345678910111213141516171819202122232425262728import java.util.concurrent.*;class myCallable implements Callable<String> { String name; public myCallable(String name) { this.name = name; } @Override public String call() throws Exception { System.out.println(this.name); return this.name; }}public class ThreadTest { public static void main(String[] args) { //创建线程池 ExecutorService es = Executors.newSingleThreadExecutor(); Future<String> future1 = es.submit(new myCallable(\"1\")); Future<String> future2 = es.submit(new myCallable(\"2\")); try { Thread.sleep(1000); System.out.println(\"return future1:\"+future1.get()); System.out.println(\"return future2:\"+future2.get()); }catch (Exception e) { e.printStackTrace(); } System.out.println(\"main\"); }} FutureTask 接口实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable 和Future接口因此FutureTask也可以直接提交给Executor执行。 当然也可以调用线程直接执行(FutureTask.run()) 采用futuretask实现多线程: 1234567891011121314151617181920212223242526272829import java.util.concurrent.*;class myCallable implements Callable<String> { String name; public myCallable(String name) { this.name = name; } @Override public String call() throws Exception { System.out.println(this.name); return this.name; }}public class ThreadTest { public static void main(String[] args) { FutureTask futureTask1= new FutureTask(new myCallable(\"1\")); FutureTask futureTask2= new FutureTask(new myCallable(\"2\")); new Thread(futureTask1).start(); new Thread(futureTask2).start(); try { Thread.sleep(1000); System.out.println(\"1:\"+futureTask1.get()); System.out.println(\"2:\"+futureTask2.get()); } catch (Exception e){ e.printStackTrace(); } System.out.println(\"main\"); }} 3.线程间同步与线程安全区别进程里面的 同步/异步 阻塞/非阻塞 概念: 1.同步与异步同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。 而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。 2.阻塞非阻塞 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态. 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 Java 线程间同步由于多个线程可能会共享相同的内存空间和资源,因此在进行多线程时,要保证多个线程合理访问资源,防止造成冲突和错误。 线程安全需要线程满足执行控制和内存可见 执行控制:线程按照人为的设想进行并发和要求执行。 内存可见:线程对内存的操作和修改对其他线程是可见的。 线程同步方法 Synchronized同步 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。 synchronized:可见性,原子性 需要注意的地方,synchronized是对象锁,对整个对象的同步代码进行加锁,未获得锁的线程,所有对同步代码块的访问的请求都被阻塞。无论该线程请求的是不是加锁的线程所访问的代码块,所有同步代码块都被加锁。 普通方法:只有获取了该对象的锁的可以访问,不影响其他对象的访问。 静态方法:只有获取了类的锁的线程可访问,所有对象均被block。 其他未被Synchronized修饰的方法,可以直接访问。 Synchronized关键字不能继承。 父类使用了 synchronized的方法,子类在继承的时候默认是不同步的 在定义接口方法时不能使用synchronized关键字 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。 Synchronized也可以对类进行同步控制。 同步方法 12345678910111213141516171819202122232425262728import java.util.concurrent.*;/** * Description: * User: jehuRen * Date: 2019-08-26 * Time: 14:56 */class myRunnable implements Runnable{ String name; public myRunnable(String name) { this.name = name; } @Override public void run() { System.out.println(this.name); System.out.println(Thread.currentThread().getId()); }}public class ThreadTest { public static void main(String[] args) { Runnable runnable = new myRunnable(\"1\"); new Thread(runnable).start(); new Thread(runnable).start(); System.out.println(\"main\"); }} 同步代码块 12345//synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下: synchronized(syncObject) { //允许访问控制的代码 } //synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行 synchronized实现机制: 基于操作系统的mutex lock互斥锁来实现的,是重量级锁,后面会详细介绍 使用volatile进行同步 volatile关键字为域变量的访问提供了一种免锁机制, 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新, 因此每次使用该域就要重新加载,而不是使用寄存器中的值,每次修改完之后要重新写入主内存 volatile不会提供任何原子操作,它也不能用来修饰final类型的变量,volatile只提供可见性,不提供原子性 volatile 可以禁止指令重排序,不缓存在cache中 1234567891011121314151617181920212223242526272829public class Counter { public volatile static int count = 0; public static void inc() { //这里延迟1毫秒,使得结果明显 try { Thread.sleep(1); } catch (InterruptedException e) { } count++; } public static void main(String[] args) { //同时启动1000个线程,去进行i++计算,看看实际结果 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { Counter.inc(); } }).start(); } //这里每次运行的值都有可能不同,可能为1000 System.out.println(\"运行结果:Counter.count=\" + Counter.count); } } CASCAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。 java采用 sun.misc.unsafe类里的CAS实现的,unsafe的实现是基于操作系统底层机器指令实现的。java.util.concurrent.atomic里面的原子类和AQS是采用CSA操作实现的。 具体的实现: 1234567891011121314151617181920212223242526272829303132private static final Unsafe unsafe = Unsafe.getUnsafe();//unsafe操作类private static final long valueOffset;//内存偏移static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField(\"value\")); } catch (Exception ex) { throw new Error(ex); }}//获取值的内存位置//volatile保证值的可见性private volatile int value;public final int incrementAndGet() { //用get出来的值+1,前面的方法是unsafe中实现的 i++。对value属性进行操作。 return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}//Unsafe中的。这个方法可以看到一直在做do-while,直到CAS成功(获取AtomicInteger对象上的value属性,然后CAS检查保证值是var5的时候将他变成var5+1)。//其中getIntVolatile和compareAndSwapInt 都是native方法,用C写的。CAS底层貌似是使用了cpu的cpxchg(compare*change)。public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;}//自旋CSA 采用unsafe的CAS 然后原子类自旋修改值,unsafe调用的是底层的系统原语的指令,所以可以保证操作的原子性。 锁实现线程同步 在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。 主要有:ReentrantLock可重入独占锁,ReentrantReadWriteLock读写锁(读共享,写独占),CountDownLatch(计数器),CyclicBarrier(回环栅栏),Samaphore(信号量) 可重入锁实例 123456789101112131415161718192021222324252627282930313233public class ReentrantLockDemo01 implements Runnable { private Lock lock = new ReentrantLock(); private int tickets = 200; @Override public void run() { while (true) { lock.lock(); // 获取锁 try { if (tickets > 0) { TimeUnit.MILLISECONDS.sleep(100); System.out.println(Thread.currentThread().getName() + \" \" + tickets--); } else { break; } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); // 释放所 } } } public static void main(String[] args) { ReentrantLockDemo01 reentrantLockDemo = new ReentrantLockDemo01(); for (int i = 0; i < 10; i++) { Thread thread = new Thread(reentrantLockDemo, \"thread\" + i); thread.start(); } }} 读写锁使用实例 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;import java.util.concurrent.locks.ReentrantReadWriteLock;public class JUCtest { private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); private Lock RWlock = new ReentrantLock(); private int tickets = 200; public void read(){ reentrantReadWriteLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName()+\":\"+tickets); } finally { reentrantReadWriteLock.readLock().unlock(); } } public void write(){ reentrantReadWriteLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName()+\":\"+tickets--); } finally { reentrantReadWriteLock.writeLock().unlock(); } } public static void main(String[] args) { JUCtest juCtest = new JUCtest(); for (int i = 0; i < 10; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { juCtest.write(); } }, \"thread\" + i); thread.start(); } for (int i = 0; i < 10; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { juCtest.read(); } },\"thread\"+i*10); thread.start(); } }} CountDownLatch 新建对象时设置计数的值 调用await()方法当前线程会挂起,等待count值减为0时继续。 调用countDown()方法可以将计数值减一 12345678910111213141516171819202122232425262728293031323334353637383940public class Test { public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(2); new Thread(){ public void run() { try { System.out.println(\"子线程\"+Thread.currentThread().getName()+\"正在执行\"); Thread.sleep(3000); System.out.println(\"子线程\"+Thread.currentThread().getName()+\"执行完毕\"); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); new Thread(){ public void run() { try { System.out.println(\"子线程\"+Thread.currentThread().getName()+\"正在执行\"); Thread.sleep(3000); System.out.println(\"子线程\"+Thread.currentThread().getName()+\"执行完毕\"); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); try { System.out.println(\"等待2个子线程执行完毕...\"); latch.await(); System.out.println(\"2个子线程已经执行完毕\"); System.out.println(\"继续执行主线程\"); } catch (InterruptedException e) { e.printStackTrace(); } }} CyclicBarrier 字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。 1234567891011121314151617181920212223242526272829303132333435public class Test { public static void main(String[] args) { int N = 4; CyclicBarrier barrier = new CyclicBarrier(N,new Runnable() { @Override public void run() { System.out.println(\"当前线程\"+Thread.currentThread().getName()); } }); for(int i=0;i<N;i++) new Writer(barrier).start(); } static class Writer extends Thread{ private CyclicBarrier cyclicBarrier; public Writer(CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; } @Override public void run() { System.out.println(\"线程\"+Thread.currentThread().getName()+\"正在写入数据...\"); try { Thread.sleep(5000); //以睡眠来模拟写入数据操作 System.out.println(\"线程\"+Thread.currentThread().getName()+\"写入数据完毕,等待其他线程写入完毕\"); cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); }catch(BrokenBarrierException e){ e.printStackTrace(); } System.out.println(\"所有线程写入完毕,继续处理其他任务...\"); } }} semaphore Semaphore翻译成字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。可以看成一个互斥锁。 123456789101112131415161718192021222324252627282930public class Test { public static void main(String[] args) { int N = 8; //工人数 Semaphore semaphore = new Semaphore(5); //机器数目 for(int i=0;i<N;i++) new Worker(i,semaphore).start(); } static class Worker extends Thread{ private int num; private Semaphore semaphore; public Worker(int num,Semaphore semaphore){ this.num = num; this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println(\"工人\"+this.num+\"占用一个机器在生产...\"); Thread.sleep(2000); System.out.println(\"工人\"+this.num+\"释放出机器\"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } }} 使用局部变量实现线程同步 同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享变量,这样当然不需要对多个线程进行同步了。 12345678910111213141516//只改Bank类,其余代码与上同public class Bank{ //使用ThreadLocal类管理共享变量account private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){ @Override protected Integer initialValue(){ return 100; } }; public void save(int money){ account.set(account.get()+money); } public int getAccount(){ return account.get(); }} 使用阻塞队列实现线程同步 前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 本小节主要是使用LinkedBlockingQueue来实现线程的同步 LinkedBlockingQueue 类常用方法LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue put(E e) : 在队尾添加一个元素,如果队列满则阻塞 size() : 返回队列中的元素个数 take() : 移除并返回队头元素,如果队列空则阻塞 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677package com.xhj.thread;import java.util.Random;import java.util.concurrent.LinkedBlockingQueue;/** * 用阻塞队列实现线程同步 LinkedBlockingQueue的使用 * * @author XIEHEJUN * */public class BlockingSynchronizedThread { /** * 定义一个阻塞队列用来存储生产出来的商品 */ private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(); /** * 定义生产商品个数 */ private static final int size = 10; /** * 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程 */ private int flag = 0; private class LinkBlockThread implements Runnable { @Override public void run() { int new_flag = flag++; System.out.println(\"启动线程 \" + new_flag); if (new_flag == 0) { for (int i = 0; i < size; i++) { int b = new Random().nextInt(255); System.out.println(\"生产商品:\" + b + \"号\"); try { queue.put(b); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(\"仓库中还有商品:\" + queue.size() + \"个\"); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } else { for (int i = 0; i < size / 2; i++) { try { int n = queue.take(); System.out.println(\"消费者买去了\" + n + \"号商品\"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(\"仓库中还有商品:\" + queue.size() + \"个\"); try { Thread.sleep(100); } catch (Exception e) { // TODO: handle exception } } } } } public static void main(String[] args) { BlockingSynchronizedThread bst = new BlockingSynchronizedThread(); LinkBlockThread lbt = bst.new LinkBlockThread(); Thread thread1 = new Thread(lbt); Thread thread2 = new Thread(lbt); thread1.start(); thread2.start(); }} 使用原子变量实现线程同步 1234567891011class Bank { private AtomicInteger account = new AtomicInteger(100); public AtomicInteger getAccount() { return account; } public void save(int money) { account.addAndGet(money); }} 无锁,偏向锁,轻量级锁,重量级锁(synchronized优化)java 对象头内容 32位JVM对象头:Mark Word(标记字段)、Klass Pointer(类型指针) Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。 Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 Monitor(监视器) Monitor record是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步。 无锁,偏向锁,轻量级锁和重量级锁 偏向锁:偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。 轻量级锁:是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。 重量级锁:若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。 乐观锁和悲观锁先说概念。对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。 而乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。 乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。 锁的区分(按照某一方面的特性) 公平锁和非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。 非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。 自旋锁和非自旋锁 在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。 而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。自旋锁的实现原理同样也是CAS。 自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。 共享锁和排他锁 独享锁和共享锁同样是一种概念。我们先介绍一下具体的概念,然后通过ReentrantLock和ReentrantReadWriteLock的源码来介绍独享锁和共享锁。 独享锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。 共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。 独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。 AQSAbstractQueuedSynchronizer(AQS),抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它。 AQS是一个抽象类,一些锁的实现依赖于内部类Sync,Sync继承AQS(AbstractQueuedSynchronizer),添加锁和释放锁的大部分操作实际上都是在Sync中实现的。它有公平锁FairSync和非公平锁NonfairSync两个子类。 AQS维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列) AQS的实现依赖内部的同步队列(FIFO双向队列),如果当前线程获取同步状态失败,AQS会将该线程以及等待状态等信息构造成一个Node,将其加入同步队列的尾部,同时阻塞当前线程,当同步状态释放时,唤醒队列的头节点。首先来看AQS最主要的三个成员变量: 12345private transient volatile Node head;//头结点private transient volatile Node tail;//尾结点private volatile int state;//同步状态变量 假设state=0表示同步状态可用(如果用于锁,则表示锁可用),state=1表示同步状态已被占用(锁被占用) 获取同步状态 假设线程A要获取同步状态(这里想象成锁,方便理解),初始状态下state=0,所以线程A可以顺利获取锁,A获取锁后将state置为1。在A没有释放锁期间,线程B也来获取锁,此时因为state=1,表示锁被占用,所以将B的线程信息和等待状态等信息构成出一个Node节点对象,放入同步队列,head和tail分别指向队列的头部和尾部(此时队列中有一个空的Node节点作为头点,head指向这个空节点,空Node的后继节点是B对应的Node节点,tail指向它),同时阻塞线程B(这里的阻塞使用的是LockSupport.park()方法)。后续如果再有线程要获取锁,都会加入队列尾部并阻塞。 释放同步状态 当线程A释放锁时,即将state置为0,此时A会唤醒头节点的后继节点(所谓唤醒,其实是调用LockSupport.unpark(B)方法),即B线程从LockSupport.park()方法返回,此时B发现state已经为0,所以B线程可以顺利获取锁,B获取锁后B的Node节点随之出队。 (注 LockSupport 实际调用的是unsafe的park和unpark方法) 主要方法: 12345678getState()//获取状态setState()//设置状态compareAndSetState()//比较并设置状态,CASboolean tryAcquire(int arg)//尝试获取互斥锁boolean tryRelease(int arg)//尝试释放互斥锁int tryAcquireShared(int arg)//尝试获取共享锁boolean tryReleaseShared(int arg)//尝试释放共享锁boolean isHeldExclusively()//判断是否独占 AQS主要使用了模板模式,使用时只要实现模板中的方法即可,自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。 4.原子类基本类型 使用原子的方式更新基本类型 AtomicInteger:整形原子类 AtomicLong:长整型原子类 AtomicBoolean:布尔型原子类 数组类型 使用原子的方式更新数组里的某个元素 AtomicIntegerArray:整形数组原子类 AtomicLongArray:长整形数组原子类 AtomicReferenceArray:引用类型数组原子类 引用类型 AtomicReference:引用类型原子类 AtomicStampedReference:原子更新引用类型里的字段原子类 AtomicMarkableReference :原子更新带有标记位的引用类型 对象的属性修改类型 AtomicIntegerFieldUpdater:原子更新整形字段的更新器 AtomicLongFieldUpdater:原子更新长整形字段的更新器 AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 5.ThreadLocal通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK中提供的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。 如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。 Thread 类中有一个 threadLocals 和 一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,我们可以把 ThreadLocalMap 理解为ThreadLocal 类实现的定制化的 HashMap。默认情况下这两个变量都是null,只有当前线程调用 ThreadLocal 类的 set或get方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的 get()、set()方法。 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法 强弱引用问题,在GC里面详细介绍。 6.并发容器 ConcurrentHashMap: 线程安全的HashMap CopyOnWriteArrayList: 线程安全的List,在读多写少的场合性能非常好,远远好于Vector. ConcurrentLinkedQueue: 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。 BlockingQueue: 这是一个接口,JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。 ConcurrentSkipListMap: 跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。 7.线程池线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。 好处: 降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。 提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 线程池创建方式 直接创建ThreadPoolExcutor对象 使用Executors工具类创建对应的线程池 线程池主要的参数: corePoolSize:核心池的大小,这个参数与后面讲述的线程池的实现原理有非常大的关系。 maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程; keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。 unit:参数keepAliveTime的时间单位,有7种取值。 workQueue:一个阻塞队列,用来存储等待执行的任务,可选择类型,影响性能 ArrayBlockingQueue;基于数组的先进先出队列,有界 LinkedBlockingQueue;基于链表的先进先出队列,无界 PriorityBlockingQueue 排序队列 无界 按照自然顺序排序,也可以冲写compareTo方法。 SynchronousQueue;无缓冲的等待队列,无界 threadFactory:线程工厂,主要用来创建线程; handler:表示当拒绝处理任务时的策略,有以下四种取值: 1234ThreadPoolExecutor.AbortPolicy;//丢弃任务并抛出RejectedExecutionException异常。 (默认)ThreadPoolExecutor.DiscardPolicy;//也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy;//丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)ThreadPoolExecutor.CallerRunsPolicy;//由调用线程处理该任务 主要的线程池类型: FixedThreadPool : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。(固定大小) SingleThreadExecutor: 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。(单个) CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。(动态调整) 三种线程池 四种拒绝策略 三种等待队列","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"SpringBoot学习","slug":"框架/Springboot","date":"2020-01-19T16:00:00.000Z","updated":"2021-01-11T19:21:00.095Z","comments":true,"path":"2020/01/20/框架/Springboot/","link":"","permalink":"http://renjiahui.cn/2020/01/20/%E6%A1%86%E6%9E%B6/Springboot/","excerpt":"","text":"SpringbootSpringboot 启动流程","categories":[],"tags":[{"name":"框架学习 Java","slug":"框架学习-Java","permalink":"http://renjiahui.cn/tags/%E6%A1%86%E6%9E%B6%E5%AD%A6%E4%B9%A0-Java/"}]},{"title":"lombok注解详解","slug":"java/lombok","date":"2020-01-14T16:00:00.000Z","updated":"2021-02-18T08:57:17.863Z","comments":true,"path":"2020/01/15/java/lombok/","link":"","permalink":"http://renjiahui.cn/2020/01/15/java/lombok/","excerpt":"","text":"原理一般我们使用的注解的@Retention一般为RUNTIME,对于这种注解,我们只在运行时加载,通过反射获取对应的注解值 lombok的注解采用@Retention的元注解为SOURCE,它会在编译时进行解析,采用的方式是Pluggable Annotation Processing API 1234567891011举例来说,现在有一个实现了Pluggable Annotation Processing API的程序A,那么使用javac编译时的具体流程如下:1. javac编译器对源码进行分析,生成一个抽象的语法树(AST)2. javac编译器运行A程序3. A程序完成逻辑,一般是修改此语法树4. javac使用修改后的语法树生成可执行的字节码文件Lomok便是通过Pluggable Annotation Processing API来实现代码生成的。 关于javac 注解详解val:用在局部变量前面,相当于将变量声明为final @NonNull:给方法参数增加这个注解会自动在方法内对该参数进行是否为空的校验,如果为空,则抛出NPE(NullPointerException) @Cleanup:自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出之前会自动清理资源,自动生成try-finally这样的代码来关闭流 @Getter/@Setter:用在属性上,再也不用自己手写setter和getter方法了,还可以指定访问范围 @ToString:用在类上,可以自动覆写toString方法,当然还可以加其他参数,例如@ToString(exclude=”id”)排除id属性,或者@ToString(callSuper=true, includeFieldNames=true)调用父类的toString方法,包含所有属性 @EqualsAndHashCode:用在类上,自动生成equals方法和hashCode方法 @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:用在类上,自动生成无参构造和使用所有参数的构造函数以及把所有@NonNull属性作为参数的构造函数,如果指定staticName = “of”参数,同时还会生成一个返回类对象的静态工厂方法,比使用构造函数方便很多 @Data:注解在类上,相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstrutor这些注解,对于POJO类十分有用 @Value:用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法 @Builder:用在类、构造器、方法上,为你提供复杂的builder APIs,让你可以像如下方式一样调用Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();更多说明参考Builder @SneakyThrows:自动抛受检异常,而无需显式在方法上使用throws语句 @Synchronized:用在方法上,将方法声明为同步的,并自动加锁,而锁对象是一个私有的属性$lock或$LOCK,而java中的synchronized关键字锁对象是this,锁在this或者自己的类对象上存在副作用,就是你不能阻止非受控代码去锁this或者类对象,这可能会导致竞争条件或者其它线程错误 @Getter(lazy=true):可以替代经典的Double Check Lock样板代码 @Log:根据不同的注解生成不同类型的log对象,但是实例名称都是log,有六种可选实现类 @CommonsLog Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); @Log Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName()); @Log4j Creates log = org.apache.log4j.Logger.getLogger(LogExample.class); @Log4j2 Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); @Slf4j Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class); @XSlf4j Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"mybatis Genrator","slug":"框架/mybatisGenerator","date":"2020-01-09T16:00:00.000Z","updated":"2021-01-11T19:20:44.228Z","comments":true,"path":"2020/01/10/框架/mybatisGenerator/","link":"","permalink":"http://renjiahui.cn/2020/01/10/%E6%A1%86%E6%9E%B6/mybatisGenerator/","excerpt":"","text":"MyBatis Generator是一个可以用来生成Mybatis dao,entity,Mapper文件的一个工具,在项目的过程中可以省去很多重复的工作,我们只要在MyBatis Generator的配置文件中配置好要生成的表名与包名,然后运行一条命令就会生成一堆文件。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE generatorConfiguration PUBLIC \"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN\"\"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd\"><!-- 配置生成器 --><generatorConfiguration><!-- 可以用于加载配置项或者配置文件,在整个配置文件中就可以使用${propertyKey}的方式来引用配置项 resource:配置资源加载地址,使用resource,MBG从classpath开始找,比如com/myproject/generatorConfig.properties url:配置资源加载地质,使用URL的方式,比如file:///C:/myfolder/generatorConfig.properties. 注意,两个属性只能选址一个; 另外,如果使用了mybatis-generator-maven-plugin,那么在pom.xml中定义的properties都可以直接在generatorConfig.xml中使用<properties resource=\"\" url=\"\" /> --> <!-- 在MBG工作的时候,需要额外加载的依赖包 location属性指明加载jar/zip包的全路径<classPathEntry location=\"/Program Files/IBM/SQLLIB/java/db2java.zip\" /> --> <!-- context:生成一组对象的环境 id:必选,上下文id,用于在生成错误时提示 defaultModelType:指定生成对象的样式 1,conditional:类似hierarchical; 2,flat:所有内容(主键,blob)等全部生成在一个对象中; 3,hierarchical:主键生成一个XXKey对象(key class),Blob等单独生成一个对象,其他简单属性在一个对象中(record class) targetRuntime: 1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample; 2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample; introspectedColumnImpl:类全限定名,用于扩展MBG--><context id=\"mysql\" defaultModelType=\"hierarchical\" targetRuntime=\"MyBatis3Simple\" > <!-- 自动识别数据库关键字,默认false,如果设置为true,根据SqlReservedWords中定义的关键字列表; 一般保留默认值,遇到数据库关键字(Java关键字),使用columnOverride覆盖 --> <property name=\"autoDelimitKeywords\" value=\"false\"/> <!-- 生成的Java文件的编码 --> <property name=\"javaFileEncoding\" value=\"UTF-8\"/> <!-- 格式化java代码 --> <property name=\"javaFormatter\" value=\"org.mybatis.generator.api.dom.DefaultJavaFormatter\"/> <!-- 格式化XML代码 --> <property name=\"xmlFormatter\" value=\"org.mybatis.generator.api.dom.DefaultXmlFormatter\"/> <!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是`反引号; --> <property name=\"beginningDelimiter\" value=\"`\"/> <property name=\"endingDelimiter\" value=\"`\"/> <!-- 必须要有的,使用这个配置链接数据库 @TODO:是否可以扩展 --> <jdbcConnection driverClass=\"com.mysql.jdbc.Driver\" connectionURL=\"jdbc:mysql:///pss\" userId=\"root\" password=\"admin\"> <!-- 这里面可以设置property属性,每一个property属性都设置到配置的Driver上 --> </jdbcConnection> <!-- java类型处理器 用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl; 注意一点,默认会先尝试使用Integer,Long,Short等来对应DECIMAL和 NUMERIC数据类型; --> <javaTypeResolver type=\"org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl\"> <!-- true:使用BigDecimal对应DECIMAL和 NUMERIC数据类型 false:默认, scale>0;length>18:使用BigDecimal; scale=0;length[10,18]:使用Long; scale=0;length[5,9]:使用Integer; scale=0;length<5:使用Short; --> <property name=\"forceBigDecimals\" value=\"false\"/> </javaTypeResolver> <!-- java模型创建器,是必须要的元素 负责:1,key类(见context的defaultModelType);2,java类;3,查询类 targetPackage:生成的类要放的包,真实的包受enableSubPackages属性控制; targetProject:目标项目,指定一个存在的目录下,生成的内容会放到指定目录中,如果目录不存在,MBG不会自动建目录 --> <javaModelGenerator targetPackage=\"com._520it.mybatis.domain\" targetProject=\"src/main/java\"> <!-- for MyBatis3/MyBatis3Simple 自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field;而不是使用setter; --> <property name=\"constructorBased\" value=\"false\"/> <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false --> <property name=\"enableSubPackages\" value=\"true\"/> <!-- for MyBatis3 / MyBatis3Simple 是否创建一个不可变的类,如果为true, 那么MBG会创建一个没有setter方法的类,取而代之的是类似constructorBased的类 --> <property name=\"immutable\" value=\"false\"/> <!-- 设置一个根对象, 如果设置了这个根对象,那么生成的keyClass或者recordClass会继承这个类;在Table的rootClass属性中可以覆盖该选项 注意:如果在key class或者record class中有root class相同的属性,MBG就不会重新生成这些属性了,包括: 1,属性名相同,类型相同,有相同的getter/setter方法; --> <property name=\"rootClass\" value=\"com._520it.mybatis.domain.BaseDomain\"/> <!-- 设置是否在getter方法中,对String类型字段调用trim()方法 --> <property name=\"trimStrings\" value=\"true\"/> </javaModelGenerator> <!-- 生成SQL map的XML文件生成器, 注意,在Mybatis3之后,我们可以使用mapper.xml文件+Mapper接口(或者不用mapper接口), 或者只使用Mapper接口+Annotation,所以,如果 javaClientGenerator配置中配置了需要生成XML的话,这个元素就必须配置 targetPackage/targetProject:同javaModelGenerator --> <sqlMapGenerator targetPackage=\"com._520it.mybatis.mapper\" targetProject=\"src/main/resources\"> <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false --> <property name=\"enableSubPackages\" value=\"true\"/> </sqlMapGenerator> <!-- 对于mybatis来说,即生成Mapper接口,注意,如果没有配置该元素,那么默认不会生成Mapper接口 targetPackage/targetProject:同javaModelGenerator type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下): 1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML; 2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中; 3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML; 注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER --> <javaClientGenerator targetPackage=\"com._520it.mybatis.mapper\" type=\"ANNOTATEDMAPPER\" targetProject=\"src/main/java\"> <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false --> <property name=\"enableSubPackages\" value=\"true\"/> <!-- 可以为所有生成的接口添加一个父接口,但是MBG只负责生成,不负责检查 <property name=\"rootInterface\" value=\"\"/> --> </javaClientGenerator> <!-- 选择一个table来生成相关文件,可以有一个或多个table,必须要有table元素 选择的table会生成一下文件: 1,SQL map文件 2,生成一个主键类; 3,除了BLOB和主键的其他字段的类; 4,包含BLOB的类; 5,一个用户生成动态查询的条件类(selectByExample, deleteByExample),可选; 6,Mapper接口(可选) tableName(必要):要生成对象的表名; 注意:大小写敏感问题。正常情况下,MBG会自动的去识别数据库标识符的大小写敏感度,在一般情况下,MBG会 根据设置的schema,catalog或tablename去查询数据表,按照下面的流程: 1,如果schema,catalog或tablename中有空格,那么设置的是什么格式,就精确的使用指定的大小写格式去查询; 2,否则,如果数据库的标识符使用大写的,那么MBG自动把表名变成大写再查找; 3,否则,如果数据库的标识符使用小写的,那么MBG自动把表名变成小写再查找; 4,否则,使用指定的大小写格式查询; 另外的,如果在创建表的时候,使用的\"\"把数据库对象规定大小写,就算数据库标识符是使用的大写,在这种情况下也会使用给定的大小写来创建表名; 这个时候,请设置delimitIdentifiers=\"true\"即可保留大小写格式; 可选: 1,schema:数据库的schema; 2,catalog:数据库的catalog; 3,alias:为数据表设置的别名,如果设置了alias,那么生成的所有的SELECT SQL语句中,列名会变成:alias_actualColumnName 4,domainObjectName:生成的domain类的名字,如果不设置,直接使用表名作为domain类的名字;可以设置为somepck.domainName,那么会自动把domainName类再放到somepck包里面; 5,enableInsert(默认true):指定是否生成insert语句; 6,enableSelectByPrimaryKey(默认true):指定是否生成按照主键查询对象的语句(就是getById或get); 7,enableSelectByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询语句; 8,enableUpdateByPrimaryKey(默认true):指定是否生成按照主键修改对象的语句(即update); 9,enableDeleteByPrimaryKey(默认true):指定是否生成按照主键删除对象的语句(即delete); 10,enableDeleteByExample(默认true):MyBatis3Simple为false,指定是否生成动态删除语句; 11,enableCountByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询总条数语句(用于分页的总条数查询); 12,enableUpdateByExample(默认true):MyBatis3Simple为false,指定是否生成动态修改语句(只修改对象中不为空的属性); 13,modelType:参考context元素的defaultModelType,相当于覆盖; 14,delimitIdentifiers:参考tableName的解释,注意,默认的delimitIdentifiers是双引号,如果类似MYSQL这样的数据库,使用的是`(反引号,那么还需要设置context的beginningDelimiter和endingDelimiter属性) 15,delimitAllColumns:设置是否所有生成的SQL中的列名都使用标识符引起来。默认为false,delimitIdentifiers参考context的属性 注意,table里面很多参数都是对javaModelGenerator,context等元素的默认属性的一个复写; --> <table tableName=\"userinfo\" > <!-- 参考 javaModelGenerator 的 constructorBased属性--> <property name=\"constructorBased\" value=\"false\"/> <!-- 默认为false,如果设置为true,在生成的SQL中,table名字不会加上catalog或schema; --> <property name=\"ignoreQualifiersAtRuntime\" value=\"false\"/> <!-- 参考 javaModelGenerator 的 immutable 属性 --> <property name=\"immutable\" value=\"false\"/> <!-- 指定是否只生成domain类,如果设置为true,只生成domain类,如果还配置了sqlMapGenerator,那么在mapper XML文件中,只生成resultMap元素 --> <property name=\"modelOnly\" value=\"false\"/> <!-- 参考 javaModelGenerator 的 rootClass 属性 <property name=\"rootClass\" value=\"\"/> --> <!-- 参考javaClientGenerator 的 rootInterface 属性 <property name=\"rootInterface\" value=\"\"/> --> <!-- 如果设置了runtimeCatalog,那么在生成的SQL中,使用该指定的catalog,而不是table元素上的catalog <property name=\"runtimeCatalog\" value=\"\"/> --> <!-- 如果设置了runtimeSchema,那么在生成的SQL中,使用该指定的schema,而不是table元素上的schema <property name=\"runtimeSchema\" value=\"\"/> --> <!-- 如果设置了runtimeTableName,那么在生成的SQL中,使用该指定的tablename,而不是table元素上的tablename <property name=\"runtimeTableName\" value=\"\"/> --> <!-- 注意,该属性只针对MyBatis3Simple有用; 如果选择的runtime是MyBatis3Simple,那么会生成一个SelectAll方法,如果指定了selectAllOrderByClause,那么会在该SQL中添加指定的这个order条件; --> <property name=\"selectAllOrderByClause\" value=\"age desc,username asc\"/> <!-- 如果设置为true,生成的model类会直接使用column本身的名字,而不会再使用驼峰命名方法,比如BORN_DATE,生成的属性名字就是BORN_DATE,而不会是bornDate --> <property name=\"useActualColumnNames\" value=\"false\"/> <!-- generatedKey用于生成生成主键的方法, 如果设置了该元素,MBG会在生成的<insert>元素中生成一条正确的<selectKey>元素,该元素可选 column:主键的列名; sqlStatement:要生成的selectKey语句,有以下可选项: Cloudscape:相当于selectKey的SQL为: VALUES IDENTITY_VAL_LOCAL() DB2 :相当于selectKey的SQL为: VALUES IDENTITY_VAL_LOCAL() DB2_MF :相当于selectKey的SQL为:SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1 Derby :相当于selectKey的SQL为:VALUES IDENTITY_VAL_LOCAL() HSQLDB :相当于selectKey的SQL为:CALL IDENTITY() Informix :相当于selectKey的SQL为:select dbinfo('sqlca.sqlerrd1') from systables where tabid=1 MySql :相当于selectKey的SQL为:SELECT LAST_INSERT_ID() SqlServer :相当于selectKey的SQL为:SELECT SCOPE_IDENTITY() SYBASE :相当于selectKey的SQL为:SELECT @@IDENTITY JDBC :相当于在生成的insert元素上添加useGeneratedKeys=\"true\"和keyProperty属性 <generatedKey column=\"\" sqlStatement=\"\"/> --> <!-- 该元素会在根据表中列名计算对象属性名之前先重命名列名,非常适合用于表中的列都有公用的前缀字符串的时候, 比如列名为:CUST_ID,CUST_NAME,CUST_EMAIL,CUST_ADDRESS等; 那么就可以设置searchString为\"^CUST_\",并使用空白替换,那么生成的Customer对象中的属性名称就不是 custId,custName等,而是先被替换为ID,NAME,EMAIL,然后变成属性:id,name,email; 注意,MBG是使用java.util.regex.Matcher.replaceAll来替换searchString和replaceString的, 如果使用了columnOverride元素,该属性无效; <columnRenamingRule searchString=\"\" replaceString=\"\"/> --> <!-- 用来修改表中某个列的属性,MBG会使用修改后的列来生成domain的属性; column:要重新设置的列名; 注意,一个table元素中可以有多个columnOverride元素哈~ --> <columnOverride column=\"username\"> <!-- 使用property属性来指定列要生成的属性名称 --> <property name=\"property\" value=\"userName\"/> <!-- javaType用于指定生成的domain的属性类型,使用类型的全限定名 <property name=\"javaType\" value=\"\"/> --> <!-- jdbcType用于指定该列的JDBC类型 <property name=\"jdbcType\" value=\"\"/> --> <!-- typeHandler 用于指定该列使用到的TypeHandler,如果要指定,配置类型处理器的全限定名 注意,mybatis中,不会生成到mybatis-config.xml中的typeHandler 只会生成类似:where id = #{id,jdbcType=BIGINT,typeHandler=com._520it.mybatis.MyTypeHandler}的参数描述 <property name=\"jdbcType\" value=\"\"/> --> <!-- 参考table元素的delimitAllColumns配置,默认为false <property name=\"delimitedColumnName\" value=\"\"/> --> </columnOverride> <!-- ignoreColumn设置一个MGB忽略的列,如果设置了改列,那么在生成的domain中,生成的SQL中,都不会有该列出现 column:指定要忽略的列的名字; delimitedColumnName:参考table元素的delimitAllColumns配置,默认为false 注意,一个table元素中可以有多个ignoreColumn元素 <ignoreColumn column=\"deptId\" delimitedColumnName=\"\"/> --> </table> </context></generatorConfiguration>","categories":[],"tags":[{"name":"Java 开发工具","slug":"Java-开发工具","permalink":"http://renjiahui.cn/tags/Java-%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"}]},{"title":"Spring学习","slug":"框架/Spring","date":"2020-01-09T16:00:00.000Z","updated":"2021-02-19T02:45:57.946Z","comments":true,"path":"2020/01/10/框架/Spring/","link":"","permalink":"http://renjiahui.cn/2020/01/10/%E6%A1%86%E6%9E%B6/Spring/","excerpt":"","text":"Spring主要框架核心模块: spring-core模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能。 spring-beans 模块提供 BeanFactory,工厂模式的微妙实现,它移除了编码式单例的需要,并且可以把配置和依赖从实际编码逻辑中解耦。 context模块建立在由core和 beans 模块的基础上建立起来的,它以一种类似于JNDI注册的方式访问对象。Context模块继承自Bean模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过Servelet容器)等功能。Context模块也支持Java EE的功能,比如EJB、JMX和远程调用等。ApplicationContext接口是Context模块的焦点。spring-context-support提供了对第三方库集成到Spring上下文的支持,比如缓存(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, Velocity)等。 spring-expression模块提供了强大的表达式语言,用于在运行时查询和操作对象图。它是JSP2.1规范中定义的统一表达式语言的扩展,支持set和get属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。 Spring 容器启动过程 1、资源定位:找到配置文件(载入配置文件/Springboot采用自动扫描) 2、BeanDefinition载入和解析(解析成BeanDefiniton) 3、BeanDefinition注册(添加到map(key,Definition)) 4、bean的实例化和依赖注入(getBean()方法进行实例化) 实例化的时间点: BeanFactory作为工厂类:在第一次使用的时候实例化 applicationContext作为容器:如果为singleton,则在容器启动的时候初始化bean并将bean放在缓存(单例池)中 如果为singleton,并且设置lazy-init(延迟加载)为true,则在第一次使用的时候实例化bean 如果为prototype Spring 主要功能IOCIOC: 控制反转即控制权的转移,将我们创建对象的方式反转了,以前对象的创建是由我们开发人员自己维护,包括依赖关系也是自己注入。使用了spring之后,对象的创建以及依赖关系可以由spring完成创建以及注入,反转控制就是反转了对象的创建方式,从我们自己创建反转给了程序创建(spring) DI: Dependency Injection 依赖注入spring这个容器中,替你管理着一系列的类,前提是你需要将这些类交给spring容器进行管理,然后在你需要的时候,不是自己去定义,而是直接向spring容器索取,当spring容器知道你的需求之后,就会去它所管理的组件中进行查找,然后直接给你所需要的组件.实现IOC思想需要DI做支持。注入方式: 1.set方式注入 2.构造方法注入 3.字段注入注入类型: 1.值类型注入 2.引用类型注入 IOC 容器 具有依赖注入功能的容器,它可以创建对象,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。是Spring框架的核心。Spring提供了两种不同的容器:Spring BeanFactory 容器(它是最简单的容器,给 DI 提供了基本的支持)和Spring ApplicationContext 容器(该容器添加了更多的企业特定的功能,例如从一个属性文件中解析文本信息的能力,发布应用程序事件给感兴趣的事件监听器的能力。)。 对象的创建时间:正常在容器初始化的时候进行创建,设置了lazy-init时,在getbean的时候进行创建。 Spring BeanFactory 容器12345678910//在Bean中配置bean的内容public class MainApp { public static void main(String[] args) { XmlBeanFactory factory = new XmlBeanFactory (new ClassPathResource(\"Beans.xml\")); //使用ClassPathResource去读取XML文件,再调用XmlBeanFactory去生成BeanFactory HelloWorld obj = (HelloWorld) factory.getBean(\"helloWorld\");//获取bean对象 obj.getMessage(); }} Application Context 是 BeanFactory 的子接口,也被成为 Spring 上下文。 Application Context 是 spring 中较高级的容器。ApplicationContext 包含 BeanFactory 所有的功能。 FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。 ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。 WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。 12345678910111213/**第一步生成工厂对象。加载完指定路径下 bean 配置文件后,利用框架提供的 FileSystemXmlApplicationContext API 去生成工厂 bean。FileSystemXmlApplicationContext 负责生成和初始化所有的对象,比如,所有在 XML bean 配置文件中的 bean。第二步利用第一步生成的上下文中的 getBean() 方法得到所需要的 bean。 这个方法通过配置文件中的 bean ID 来返回一个真正的对象。一旦得到这个对象,就可以利用这个对象来调用任何方法。*/public class MainApp { public static void main(String[] args) { ApplicationContext context = new FileSystemXmlApplicationContext (\"Beans.xml\"); HelloWorld obj = (HelloWorld) context.getBean(\"helloWorld\"); obj.getMessage(); }} Spring 配置元数据配置元数据:bean 定义包含称为配置元数据的信息,下述容器也需要知道配置元数据: 如何创建一个 bean bean 的生命周期的详细信息 bean 的依赖关系 提供配置元数据给Spring的方法: 基于 XML 的配置文件 基于注解的配置 基于 Java 的配置 Spring bean作用域:singleton(容器中只有一个实例存在,单例模式实现),prototype(每次都返回一个新的实例),request(每次Http请求创建新的bean),session(同一个HTTP Session共享一个Bean,不同Session使用不同的Bean),global-session(global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。),默认为singleton,可采用@Scope注解或者xml里面的scope属性进行修改。 Bean生命周期 Bean的生命周期可以表达为:Bean的定义——Bean的初始化——Bean的使用——Bean的销毁 在bean初始化前后进行操作第一种:通过@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作第二种是:通过 在xml中定义init-method 和 destory-method方法第三种是: 通过bean实现InitializingBean和 DisposableBean接口 Constructor > @PostConstruct > InitializingBean > init-method 后置处理器 BeanPostProcessor,实现postProcessBeforeInitialization和postProcessAfterInitialization方法来实现bean初始化之前和之后的逻辑。 beanFactory 和FactoryBeanbeanFactory是Spring容器的一个组件,负责来构建Bean的一个工厂,而FactoryBean则表示,要创建的这个bean是一个工厂类,用来生产其他bean的。 依赖注入方式基于构造函数进行注入 12345678<!-- Definition for textEditor bean --><bean id=\"textEditor\" class=\"com.tutorialspoint.TextEditor\"> <constructor-arg ref=\"spellChecker\"/></bean><!-- Definition for spellChecker bean --><bean id=\"spellChecker\" class=\"com.tutorialspoint.SpellChecker\"></bean> 也可以使用p-namespace注入 123<bean id=\"bar\" class=\"x.y.Bar\"/><bean id=\"baz\" class=\"x.y.Baz\"/><bean id=\"foo\" class=\"x.y.Foo\" c:bar-ref=\"bar\" c:baz-ref=\"baz\" c:email=\"foo@bar.com\"/> 基于设值函数进行注入 12345678 <!-- Definition for textEditor bean --> <bean id=\"textEditor\" class=\"com.tutorialspoint.TextEditor\"> <property name=\"spellChecker\" ref=\"spellChecker\"/> </bean>//基于值注入 <!-- Definition for spellChecker bean --> <bean id=\"spellChecker\" class=\"com.tutorialspoint.SpellChecker\"> </bean> 也可以使用p-namespace注入 12345678<bean id=\"john-classic\" class=\"com.example.Person\" p:name=\"John Doe\" p:spouse-ref=\"jane\"/></bean><bean name=\"jane\" class=\"com.example.Person\" p:name=\"John Doe\"/></bean> Spring 中的事件处理ContextRefreshedEvent ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。ContextStartedEvent当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。ContextStoppedEvent 当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作 ContextClosedEvent 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。RequestHandledEvent 这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。 通过 ApplicationEvent 类和 ApplicationListener 接口来提供在 ApplicationContext 中处理事件。 若Bean实现了ApplicationListener 接口,并注册了,则在对应事件发生时,会通知该bean,即该Bean会监听事件。 自定义事件:通过继承ApplicationEvent接口自定义事件,同时还需要实现ApplicationEventPublisherAware接口来触发自定义的事件。 AOPAOP 即 Aspect Oriented Program 面向切面编程,将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能。 Spring AOP实现原理,如果目标类是接口则使用JDK动态代理技术,否则则使用Cglib进行代理 AspectJ是语言级别的AOP实现,扩展了Java语言,定义了AOP语法,能够在编译期提供横切代码的织入,所以它有专门的编译器用来生成遵守Java字节码规范的Class文件。 相关术语: 1、Advice 表示通知。是切面的具体实现方法:前置增强、后置增强、环绕增强、异常抛出增强、最终增强等类型2、切入点 筛选出的连接点3、连接点 允许进行通知的地方,方法的前后等4、切面 切入点和通知的结合就是切面,在特定的切入点上实现特定的功能。5、目标对象 就是要被通知的对象,也就是真正的业务逻辑6、AOP代理 AOP框架创建的对象,包含通知。7、织入 把切面应用到目标对象上构建新的代理对象的过程 Spring提供了3种类型的AOP支持: 基于代理的经典SpringAOP 需要实现接口,手动创建代理(目前已经不建议使用) 纯POJO切面 使用XML配置,aop命名空间 1234567891011121314151617181920212223242526272829<aop:config> <aop:aspect id=\"myAspect\" ref=\"aBean\"> <aop:pointcut id=\"businessService\" expression=\"execution(* com.xyz.myapp.service.*.*(..))\"/> <!-- a before advice definition --> <aop:before pointcut-ref=\"businessService\" method=\"doRequiredTask\"/> <!-- an after advice definition --> <aop:after pointcut-ref=\"businessService\" method=\"doRequiredTask\"/> <!-- an after-returning advice definition --> <!--The doRequiredTask method must have parameter named retVal --> <aop:after-returning pointcut-ref=\"businessService\" returning=\"retVal\" method=\"doRequiredTask\"/> <!-- an after-throwing advice definition --> <!--The doRequiredTask method must have parameter named ex --> <aop:after-throwing pointcut-ref=\"businessService\" throwing=\"ex\" method=\"doRequiredTask\"/> <!-- an around advice definition --> <aop:around pointcut-ref=\"businessService\" method=\"doRequiredTask\"/> ... </aop:aspect></aop:config><bean id=\"aBean\" class=\"...\">...</bean> @AspectJ注解驱动的切面 使用注解的方式,这是最简洁和最方便的! 123456789101112131415161718192021222324252627282930//xml文件里开启对应的aspectj注解<aop:aspectj-autoproxy/>//切面逻辑@Aspect public class SleepHelper { public SleepHelper(){ } @Pointcut(\"execution(* *.sleep())\") public void sleeppoint(){} @Before(\"sleeppoint()\") public void beforeSleep(){ System.out.println(\"睡觉前要脱衣服!\"); } @AfterReturning(\"sleeppoint()\") public void afterSleep(){ System.out.println(\"睡醒了要穿衣服!\"); } } Spring只使用了aspectJ的注解,并不需要引入aspecJ的其他依赖。 切点表达式1execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。 修饰符模式指的是public、private、protected,异常模式指的是NullPointException等。 通配符 1*`通配符,该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。 1.. 通配符,该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。 可以使用&&、||、!、三种运算符来组合切点表达式,表示与或非的关系。 SpringMVCSpringMVC运行原理流程说明: (1)客户端(浏览器)发送请求,直接请求到DispatcherServlet(前端控制器)。 (2)DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。 (3)解析到对应的Handler后,开始由HandlerAdapter适配器处理。 (4)HandlerAdapter会根据Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。 (5)处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是个逻辑上的View。 (6)ViewResolver会根据逻辑View查找实际的View。 (7)DispaterServlet把返回的Model传给View。 (8)通过View返回给请求者(浏览器) SpringMVC 拦截器和过滤器Fliter依赖Servlet,Interceptor 依赖框架 常用注解@Autowired Spring 容器查找并注入一个bean对象 按类去匹配 @Required 注释应用于 bean 属性的 setter 方法 @Qualifier 指定注入一个bean对象 按名字去匹配 @Resource(1) @Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配(2) 指定了name或者type则根据指定的类型去匹配bean(3) 指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错 @Configuration 带有 @Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源 @import 注解允许从另一个配置类中加载 @Bean 定义。 @Bean @Bean 注解告诉 Spring,一个 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。 @Scope(“”)设置bean的作用域 @Component/@Repository/@Service/@ControllerComponent 通用beanRepository 对应数据访问层BeanService 对应业务逻辑层beanController 对应表现层的Bean @refrence与@resource @ComponentScan 启动组件扫描,默认扫描同一包下的组件 前者是dubbo注解,后者是spring 的。后者@resource很简单就是注入资源,与@Autowired比较接近,只不过是按照变量名(beanid)注入。@reference也是注入,但是一般用来注入分布式的远程服务对象,需要配合dubbo配置使用。他们的区别就是一个是本地spring容器,另一个是把远程服务对象当做spring容器中的对象一样注入。 java注解注解定义,注解是什么 源代码的元数据,代码标签, 附属品依赖于其他元素(方法,类,属性) 本身没有任何作用,由外部程序解析产生作用 注解携带的信息,对于不同修饰的对象有不同的意义,对于类而言,注解相当于类实现的一个接口,对于方法而言,注解相当于一个变量,对于属性而言,注解相当于一个值。 Spring 设计模式 工厂模式 使用beanFactory,ApplicationContext创建对象 单例模式 默认的bean都是创建单例的,bean的其他作用域,prototype,request,session,global-session 代理模式 SpringAOP使用的是动态代理,如果需要代理的对象是基于接口的,则使用JDK代理,如果是没有实现接口的对象则使用cglib进行代理。 模板模式 spring提供了jdbcTemplate使用了模板模式。 观察者模式 spring的event机制和监听机制是观察者模式的实现 适配器 装饰者 Spring 三级缓存","categories":[],"tags":[{"name":"框架学习 Java","slug":"框架学习-Java","permalink":"http://renjiahui.cn/tags/%E6%A1%86%E6%9E%B6%E5%AD%A6%E4%B9%A0-Java/"}]},{"title":"UML类图","slug":"其他/UML类图","date":"2019-12-31T16:00:00.000Z","updated":"2021-01-11T19:21:40.909Z","comments":true,"path":"2020/01/01/其他/UML类图/","link":"","permalink":"http://renjiahui.cn/2020/01/01/%E5%85%B6%E4%BB%96/UML%E7%B1%BB%E5%9B%BE/","excerpt":"","text":"UML类图的元素1.类类包括:类的属性和类的方法(抽象方法用斜体表示) 还有作用域 + public -private # protected 2.接口接口是特殊的类,只可以被实现,不可以被实例化。 3.类之间的关系实线三角指向父类(泛化)代码实现:子类继承父类 虚线三角指向接口(实现)代码实现:接口实现 空心菱形能分离而独立存在(聚合)代码实现:成员变量,个体可以独立于整体存在,菱形在整体对象一侧。 实心菱形精密关联不可分,组合代码实现:成员变量,个体依赖于整体存在,菱形在整体侧。 实线箭头指向关联代码实现:成员变量,可以有多对多和一对一,一对多的关系,是一个类可以拥有另一个类的方法和特性 虚线箭头指向依赖代码实现:一个类使用另一个类作为局部变量,方法的参数或者调用的 箭头指向被调用的对象 聚合和组合的联系与区别: 都是关联关系,都可以表示整体与部分的关系,而聚合的部分对象是可以独立于整体存在的,组合的部分对象是无法独立于对象存在的,只有组成了整体,部分才有意义。","categories":[],"tags":[{"name":"开发工具","slug":"开发工具","permalink":"http://renjiahui.cn/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"}]},{"title":"dubbo学习","slug":"中间件/dubbo","date":"2019-12-19T16:00:00.000Z","updated":"2021-01-11T19:22:02.978Z","comments":true,"path":"2019/12/20/中间件/dubbo/","link":"","permalink":"http://renjiahui.cn/2019/12/20/%E4%B8%AD%E9%97%B4%E4%BB%B6/dubbo/","excerpt":"","text":"Dubbo概述Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC 框架,它提供了三大核心能力: 面向接口的远程方法调用 智能容错和负载均衡 服务自动注册和发现 简单来说 Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。 RPC原理RPC定义:远程过程调用,是一种网络协议,他允许运行于一台计算机的程序去调用运行于另一台计算机的程序。而开发人员无需为这个过程额外编写代码。(对于java而言可以理解为调用远程服务器上的方法)。类似于java的RMI(远程方法调用) RPC调用过程时序图 soa架构dubbo和soa架构(Service Oriented Architecture 面向服务的架构)的流行有很大的关系 soa架构图 上述节点简单说明: Provider: 暴露服务的服务提供方 Consumer: 调用远程服务的服务消费方 Registry: 服务注册与发现的注册中心 Monitor: 统计服务的调用次数和调用时间的监控中心 Container: 服务运行容器 调用关系说明: 服务容器负责启动,加载,运行服务提供者。 服务提供者在启动时,向注册中心注册自己提供的服务。 服务消费者在启动时,向注册中心订阅自己所需的服务。 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 dubbo架构 每层具体结构解析 服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。 配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。 服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。 服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。 集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只需要与一个服务提供方进行交互。 监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。 远程调用层(Protocol):封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。 信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。 网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。 数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。 负载均衡策略: Random LoadBalance(默认,基于权重的随机负载均衡机制) RoundRobin LoadBalance(不推荐,基于权重的轮询负载均衡机制) LeastActive LoadBalance(最少活跃调用数负载均衡机制) ConsistentHash LoadBalance(一致性hash均衡策略) Dubbo服务调用过程 首先服务消费者通过代理对象 Proxy 发起远程调用,接着通过网络客户端 Client 将编码后的请求发送给服务提供方的网络层上,也就是 Server。Server 在收到请求后,首先要做的事情是对数据包进行解码。然后将解码后的请求发送至分发器 Dispatcher,再由分发器将请求派发到指定的线程池上,最后由线程池调用具体的服务。这就是一个远程调用请求的发送与接收过程。至于响应的发送与接收过程,这张图中没有表现出来。对于这两个过程,我们也会进行详细分析。","categories":[],"tags":[{"name":"Java 中间件","slug":"Java-中间件","permalink":"http://renjiahui.cn/tags/Java-%E4%B8%AD%E9%97%B4%E4%BB%B6/"}]},{"title":"分布式系统一致性协议","slug":"其他/一致性共识协议","date":"2019-12-03T16:00:00.000Z","updated":"2021-01-11T18:51:31.339Z","comments":true,"path":"2019/12/04/其他/一致性共识协议/","link":"","permalink":"http://renjiahui.cn/2019/12/04/%E5%85%B6%E4%BB%96/%E4%B8%80%E8%87%B4%E6%80%A7%E5%85%B1%E8%AF%86%E5%8D%8F%E8%AE%AE/","excerpt":"","text":"为了解决分布式系统的一致性问题,在长期的探索研究过程中,涌现出了一大批经典的一致性协议和算法,其中最著名的就是二阶段提交协议、三阶段提交协议和Paxos算法。 2PC与3PC在分布式系统中,每一个机器节点虽然都能够明确地知道自己在进行事务操作过程中的结果是成功或失败,但却无法直接获取到其他分布式节点的操作结果。因此,当一个事务操作需要跨越多个分布式节点的时候,为了保持事务处理的ACID特性,就需要引入一个称为”协调者“的组件来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点则被称为“参与者”。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务真正进行提交。基于这个思想,衍生出了二阶段提交和三阶段提交两种协议。 2PC2PC,是Two-Phase Commit的缩写,即二阶段提交,是计算机网络尤其是在数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务处理过程中能够保持原子性和一致性而设计的一种算法。通常,二阶段提交协议也被认为是一种强一致性协议,用来保证分布式系统数据的一致性。 2PC执行流程二阶段提交协议是将事务的提交过程拆分成了两个阶段来进行处理,其执行流程如下: 阶段一:提交事务请求 1、事务询问 协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者响应。 2、执行事务 各参与者节点执行事务操作,并将Undo和Redo信息记入事务日志中。 3、各参与者向协调者反馈事务询问的响应 如果参与者成功执行了事务操作,那么就反馈给协调者Yes响应,表示事务可以执行;如果参与者没有成功执行事务,那么就反馈给协调者No响应,表示事务不可以执行。 阶段二:执行事务提交协调者会根据各参与者的反馈情况来决定最终是否可以进行事务提交操作,正常情况下,包含以下两种可能: 执行事务提交:假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务提交。 1、 发送提交请求 协调者向所有参与者节点发出Commit请求。 2、事务提交 参与者接收到Commit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源。 3、 反馈事务提交结果 参与者在完成事务提交之后,向协调者发送Ack消息。 4、完成事务 协调者接收到所有参与者反馈的Ack消息后,完成事务。 中断事务:假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。 1、发送回滚请求 协调者向所有参与者节点发出Rollback请求。 2、事务回滚 参与者接收到Rollback请求后,会利用其在阶段一中记录的Undo信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。 3、反馈事务回滚结果 参与者在完成事务回滚之后,向协调者发送Ack消息。 4、中断事务 协调者接收到所有参与者反馈的Ack消息后,完成事务中断。 简单地讲,二阶段提交将一个事务的处理过程分为了投票和执行两个阶段,其核心是对每个事务都采用先尝试后提交的处理方式,因此也可以将二阶段提交看作一个强一致性的算法。 2PC存在的问题1、同步阻塞 在执行过程中,所有参与该事务操作的逻辑都处理于阻塞状态。即节点之间在等待对方的相应消息时,它将什么也做不了。特别是,当一个节点在已经占有了某项资源的情况下,为了等待其他节点的响应消息而陷入阻塞状态时,当第三个节点尝试访问该节点占有的资源时,这个节点也将连带陷入阻塞状态。 2、 单点问题 一旦协调者出现问题,那么整个二阶段提交流程将无法运转,更为严重的是,如果协调者是在阶段二中出现问题的话,那么其他参与者将会一直处于锁定事务资源的状态中,而无法继续完成事务操作。 3、 数据不一致 当协调者向所有的参与者发送Commit请求之后发生了局部网络异常或者是协调者在尚未发送完Commit请求之前自身发生了崩贵,导致最终只有部分参与者收到了Commit请求,于是整个分布式系统便出现了数据不一致性现象。 3、太过保守 二阶段提交没有设计较为完善的容错机制,任意一个节点的失败都会导致整个事务的失败。 3PC3PC,是Three-Phase Commit的缩写,即三阶段提交,是2PC的改进版。由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议。 3PC执行流程阶段一:CanCommit 1、事务询问。 协调者向所有的参与者发送一个包含事务内容的canCommit请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。 2、各参与者向协调者反馈事务询问的响应。 参与者在接收到来自协调者的canCommit请求后,正常情况下,如果其自身认为可以顺利执行事务,那么会反馈Yes响应,并进入预备状态,否则反馈No响应。 阶段二:PreCommit协调者会根据各参与者的反馈情况来决定最终是否可以进行事务提交操作,正常情况下,包含以下两种可能: 执行事务预提交;假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。 1、发送预提交请求 协调者向所有参与者节点出preCommit的请求,并进入prepared阶段。 2、事务预提交 参与者接收到preCommit请求后,会执行事务操作,并将Undo和Redo信息记录到事务日志中。 3、各参与者向协调者反馈事务执行的响应 如果参与者成功执行了事务操作,那么就会反馈给协调者Ack响应,同时等待最终的指令:提交(commit)或中止(abort)。 中断事务:假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有者的反馈响应,那么就会中断事务。。 1、发送中断请求 协调者向所有参与者节点发生abort请求。 2、中断事务 无论是收到来自协调者的abort请求,或者是在等待协调者请求过程中出现超时,参与者都会中断事务。 阶段三:doCommit该阶段将进行真正的事务提交,会存在以下两种可能的情况。 执行提交 1、发送提交请求 进入这一阶段,假如协调者处于正常状态,并且它接收到了来自所有参与者的Ack响应,那么它将从“预提交”状态转换到“提交”状态,并向所有参与者发送doCommit请求。 2、事务提交 参与者接收到doCommit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源。 3、反馈事务提交结果 参与者在完成事务提交之后,向协调者发送Ack消息。 4、完成事务 协调者接收到所有参与者反馈的Ack消息后,完成事务。 中断事务 进入这一阶段,假设协调者处于正常工作状态,并且有任意一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应。 1、发送中断请求 协调者向所有的参与者节点发送abort请求。 2、事务回滚 参与者接收到abort请求后,会利用其在阶段二中记录的Undo信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。 3、反馈事务回滚结果 参与者在完成事务会滚之后,向协调者发送Ack消息。 4、中断事务 协调者接收到所有参与者反馈的Ack消息后,中断事务。 在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者abort请求时,在等待超时之后,会继续进行事务的提交。即当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到协调者的doCommit或者abort请求,但事务仍然会提交。 3PC存在的问题三阶段提交协议与两阶段提交协议有两个主要的不同: 增加了一个询问阶段,询问阶段可以确保尽可能早的发现无法执行操作而需要中止的行为,但是它并不能发现所有的这种行为,只会减少这种情况的发生。 在准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,协调者和参与者都继续提交事务,默认为成功,这也是根据概率统计上超时后默认成功的正确性最大。 三阶段提交协议在去除阻塞的同时也引入了新的问题,那就是在参与者接收到preCommit消息后,如果网络出现分区,此时协调者所在的节点和参与者无法进行正常的网络通信,在这种情况下,该参与者依然会进行事务的提交,这必然出现数据的不一致性。 Paxos算法Paxos算法是一种基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一。 问题描述 在古希腊的有一个叫做Paxos的小岛,岛上采用会议的形式来通过法令,议会中的议员通过信使进行消息的传递。值得注意的是,议员和信使都是兼职的,他们随时有可能会离开议会厅,并且信使可能会重复的传递信息,也可能一去不复返。因此,议会协议要保证在这个情况下法令仍然能够正确的产生,并且不会出现冲突。 首先将议员的角色分为Proposer,Acceptor,和Learner(允许身兼数职)。Proposer提出提案,提案信息包括提案编号和提议的value;Acceptor收到提案后可以接受(accept)提案,如果提案获得多数Acceptors的接受,则称该提案被批准(chosen);Learner只能“学习”被批准的提案。划分角色后,就可以更精确的定义问题: 决议(value)只有在被Proposers提出后才能被批准(未经批准的决议称为“提案”(proposal); 在一次Paxos算法的执行实例中,只批准(chosen)一个value; learners只能获得被批准(chosen)的value。 由上面的三个语义可演化为下面几个约束: P1:一个Acceptor必须接受(accept)第一次收到的提案。 P2:一旦一个具有value v的提案被批准(chosen),那么之后批准(chosen)的提案必须具有value v。 P2a:一旦一个具有value v的提案被批准(chosen),那么之后任何Acceptor再次接受(accept)的提案必须具有value v。 P2b:一旦一个具有value v的提案被批准(chosen),那么以后任何Proposer提出的提案必须具有value v。 P2c:如果一个编号为n的提案具有value v,那么存在一个多数派,要么他们中所有人都没有接受(accept)编号小于n的任何提案,要么他们已经接受(accept)的所有编号小于n的提案中编号最大的那个提案具有value v。 P1a:当且仅当Acceptor没有回应过编号大于n的prepare请求时,Acceptor接受(accept)编号为n的提案。 Paxos算法内容决议的提出与批准阶段一(决议提出) 1、Proposer选择一个提案编号M,然后向Acceptors的某个超过半数的子集成员发送编号为M的prepare请求。 2、如果一个Acceptor接收到一个编号为M的pepare请求,且编号M大于该Acceptor已经响应的所有prepare请求的编号,那么它就会将它已经批准过的最大编号的提案作为响应反馈给Proposer,同时该Acceptor会承诺不会再批准任何编号小于M的提案。 阶段二(批准阶段) 1、如果Proposer收到来自半数以上的Acceptor对其发出的编号为M的prepare请求的响应,那么它就会发送一个针对[M,V]提案的accept请求给Acceptor。注意,V的值就是收到响应中编号最大的提案的值,如果响应中不包含任何提案,那么它就是任意值。 2、如果Acceptor收到这个针对[M,V]提案的Accept请求,只要该Acceptor尚未对编号大于M的prepare请求做出响应,它就可以通过这个提案。 提案的获取如何让Learner获取提案,大体可以有以下几种方案: 方案一 Learner获取一个已经被选定的提案的前提是,该提案已经被半数以上的Acceptor批准了。因此,最简单的做法就是一旦Acceptor批准了一个提案,就将该提案发送给所有的Learner。这种做法虽然可以让Learner尽快地获取被选定的提案,但是却需要让每个Acceptor与所有Learner逐个进行一次通信,通信的次数至少为二者个数的乘积。 方案二 我们可以让所有的Acceptor将它们对提案的批准情况,统一发送给一个特定的Learner(这样的Learner称为“主Learner”),假定Learner之间可以通过消息通信来互相感知提案的选定情况。当主Learner被通知一个提案被选定时,它会负责通知其他的Learner。 方案二虽然需要多一个步骤才能将提案通知到所有的Learner,但其通信次数却大大减少了,通常只是Acceptor和Learner的个数总和。但同时,该方案引入了一个新的不稳定因素;主Learner随时可能出现故障。 方案三 将主Learner的范围扩大,即Acceptor可以将批准的提案发送给一个特定的Learner集合,该集合中的每个Learner都可以在一个提案被选定后通知所有其他的Learner。这个Learner集合中的Learner个数越多,可靠性就越好,但同时网络通信的复杂度也就越高。 通过选取主Proposer保证算法的活性假设存在这样一种极端情况,有两个Proposer依次提出了一系列编号递增的议案,但是最终都无法被选定,具体流程如下: Proposer P1提出了一个编号为M1的提案,并完成了上述阶段一的流程。但与此同时,另外一个Proposer P2提出了一个编号为M2(M2>M1)的提案,同样也完成了阶段一的流程,于是Acceptor已经承诺不再批准编号小于M2的提案了。因此,当P1进入阶段二的时候,其发出的accept请求将被Acceptor忽略,于是P1再次进入阶段一并提出了一个编号为M3,(M3>M2)的提案,而这又导致P2在第二阶段的accept请求被忽略,以此类推,提案的选定过程将陷入死循环。 为了保证Paxos算法流程的可持续性,以避免陷入上述提到的“死循环”,就必须选择一个主Proposer,并规定只有主Proposer才能提出议案。这样一来,只要主Proposer和过半的Acceptor能够正常进行网络通信,那么但凡主Proposer提出一个编号更高的提案被提出或正在接受批准,那么它会丢弃当前这个编号较小的提案,并最终能够选出一个编号足够大的提案。因此,如果系统中有足够多的组建(包括Proposer、Acceptor和其他网络通信组建)能够正常工作,那么通过选择一个主Proposer,整套Paxos算法流程就能够保持活性。 ZAB 协议待补充。。 参考资料Paxos算法-维基百科 从Paxos到Zookeeper分布式一致性原理与实践","categories":[],"tags":[{"name":"分布式","slug":"分布式","permalink":"http://renjiahui.cn/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"}]},{"title":"JVM & GC","slug":"java/JVMandGC","date":"2019-10-09T16:00:00.000Z","updated":"2021-02-23T07:05:19.784Z","comments":true,"path":"2019/10/10/java/JVMandGC/","link":"","permalink":"http://renjiahui.cn/2019/10/10/java/JVMandGC/","excerpt":"","text":"内存空间线程私有的: 程序计数器 虚拟机栈 本地方法栈 线程共享的: 堆 方法区 直接内存 (非运行时数据区的一部分) 1.8以后方法区改为了元空间,并放在了直接内存中。 程序计数器程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。 Java 虚拟机栈与程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。 Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。 StackOverFlowError: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 异常。 OutOfMemoryError: 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 异常。 本地方法栈和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。 本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。 方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。 堆Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。 方法区方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。 方法区和永久代的关系:JVM规范定义有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到 java.lang.OutOfMemoryError。你可以使用 -XX:MaxMetaspaceSize 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。-XX:MetaspaceSize 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。 元空间 (MetaSpace)类的版本、字段、方法、接口等描述信息,类元信息 运行时常量池常量池信息(用于存放编译期生成的各种字面量和符号引用)运行时常量池分配在元空间中,字符串常量池放在堆中 java 符号引用和直接引用符号引用一般放在常量池中,在编译时,会用符号引用占位,一般用限定符表示,在实际解析的时候才会转化为真正的对象地址。 直接引用: 1:直接指向目标的指针。(个人理解为:指向对象,类变量和类方法的指针) 2:相对偏移量。(指向实例的变量,方法的指针) 3:一个间接定位到对象的句柄。 直接内存分配的堆外内存,元空间,(字符串常量池分配在堆中) Hotspot虚拟机对象在hotspot虚拟机中,对象的存储布局可以包括三部分:对象头,实例数据,对齐填充 对象头包括: 1.对象自身运行时数据:hashcode,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等 2.类型指针:对象指向他的类型元数据的指针 3.实例数据 JVM对象创建过程以HotSpot虚拟机为例 类的加载,连接和初始化在使用某个类之前,JVM会确保这个类被加载,连接和初始化。 加载 将类的二进制文件读取到内存中,放入方法区内(1.8为元空间)并在堆区创建一个java.lang.Class对象(符号引用?) 连接 验证 检查加载的二进制码是否符合类的规范,包括结构,语法,字节码是否合法,兼容性检查等 准备 为类的静态变量分配内存,并将其初始化为默认值。(默认值) 解析 将符号引用替换为直接引用 初始化 在初始化阶段,JVM执行类初始化语句,为类的静态变量赋予初始值。在程序中,静态变量的初始化有两种途径:一是在静态变量的声明处进行初始化;二是在静态代码块中进行初始化。静态变量的声明语句,以及静态代码块都被看作类初始化语句,JVM会按照初始化语句在类文件中的的书写顺序依次执行它们。 Java虚拟机初始化一个类包含以下步骤: 1.假如这个类还没有被加载和连接,那么先进行加载和连接。 2.假如类中存在直接父类,并且这个父类还没有被初始化,那么就先初始化直接父类。 3.假如类中存在初始化语句,那么就依次执行。 初始化时机,主要有以下这些场景会出发类的初始化(主动使用类或接口时才会出发) 01.创建类的实例。包括new关键字来创建,或者通过反射、克隆及反序列化方式来创建实例。 02.调用类的静态方法。 03.访问某个类或接口的静态变量,或者对该静态变量赋值。 04.使用反射机制来创建某个类或接口对应的java.lang.Class对象。例如Class.forName(“Test”)操作,如果系统还未初始化Test类,这波操作会导致该Test类被初始化,并返回Test类对应的java.lang.Class对象。 05.初始化一个类的子类,该子类所有的父类都会被初始化。 06.JVM启动时被标明为启动类的类(直接使用java.exe命令运行某个主类)。例如对于“java Test”命令,Test类就是启动类(主类),JVM会先初始化这个主类。 之后就是类的初始化,执行类的构造方法。 finilize() 拯救对象,只触发一次 类加载器和双亲委派模型类加载器: 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。 其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如: 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\\lib\\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。 双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。 自定义类加载器 继承ClassLoader类然后覆盖气findClass(String name)方法,指定类名称,返回对类的引用。 垃圾回收的判断算法引用计数法和可达性分析 GCroots对象 引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。存在的问题:循环引用 可达性分析:从一些GC roots 的对象为起点,从这些结点向下搜索,结点走过的路径为引用链,如果一个对象到GC roots没有引用链的话,证明这个对象是不可用的,可进行垃圾回收的。 GC roots的对象: + 栈中引用的对象 + 静态属性引用的对象 + 常量引用的对象 + 本地方法栈引用的对象 + 内部引用,系统类加载器 + 同步锁持有的对象java 的引用分析引用的类别:强引用,软引用,弱引用,虚引用 强引用 是指创建一个对象并把这个对象赋给一个引用变量,类似“Object obj=new Object()”这类的引用,普遍存在的引用。 软引用 如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;采用softReference 创建 弱引用 弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。 虚引用 果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。采用 java.lang.ref.PhantomReference表示。虚引用必须联合引用队列使用 对于软引用和弱引用和虚引用,我们希望当一个对象被gc掉的时候通知用户线程,进行额外的处理时,就需要使用引用队列了。ReferenceQueue即这样的一个对象,当一个obj被gc掉之后,其相应的包装类,即ref对象会被放入queue中。我们可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理等。 软弱引用,在引用对象被回收后进入引用队列,虚引用在回收前加入引用队列? 软弱引用在回收后加入引用队列是为了在引用对象GC掉后通知进行 方法区垃圾收集常量的回收:当常量没有地方引用时回收 加载的类型的回收: + 所有实例已经回收 + 类加载器已经回收 + Class没有被引用,没办法通过反射访问收集算法分代收集理论: 大部分对象都是朝生夕灭 熬过越多次垃圾收集的对象越难消亡 根据这两个理论,垃圾收集器的原则,将java堆划分不同的区域,根据其年龄进行回收,提高垃圾收集的效率和开销 标记清除 效率问题,碎片问题 复制算法 容量保证问题 标记整理 标记+复制 分代整理 新生代复制算法 标记清除/整理算法 CMS+ParNew ParNew 多线程 新生代垃圾收集器,并行 CMS 老年代垃圾回收 标记清除(哈啰使用的垃圾收集器搭配) 根节点枚举在OopMap中记录引用,垃圾收集器不需要去遍历GC roots 堆内存分配策略: 对象优先在eden区上分配 大对象直接进入老年代 长期存活的对象进入老年代 分代垃圾收集策略: 新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区。 大部分对象在eden区生成,当发生垃圾回收时,先将eden区存活对象复制到一个survivor0区,然后情况eden区。如果s0也放满了,将eden和s0去的存活对象,放到s1区,若s1无法存放s0和eden的对象时,则将对象放入老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。 新生代的GC叫做minorGC,来年代的叫做Major GC即Full GC 垃圾收集器新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge 老年代收集器使用的收集器:Serial Old、Parallel Old、CMS Serial收集器(复制算法) 新生代单线程收集器,标记和清理都是单线程,优点是简单高效。 Serial Old收集器(标记-整理算法) 老年代单线程收集器,Serial收集器的老年代版本。 Serial 系列的收集器:优点没有线程交互的开销,简单高效,需要有一个停顿时间,stop the world ParNew收集器(复制算法) 新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。可以和CMS配合。新生代首选垃圾收集器 Paralled系列CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。Paralled更关注应用的吞吐量,所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge收集器(复制算法) 并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。 Parallel Old收集器(标记-整理算法) Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先 CMS(标记清除) 初始标记 stop the world 标记直接与GCroot相连的对象 并发标记(其他线程并行)所标记出来的对象开始逐步遍历这些对象(与GCRoot直接相连或与存活的青年代对象直接相关联的对象)的所引用的对象,并标记 这些对象 并发预清理 减少重标记(Remark)步骤Stop-the-World的时间,由于前面的并发标记为并发的,因此因为会发生改变,改变的空间标记为脏块,将脏块重新标记,使空间变得clean。(可省) 重新标记(系统停顿)由于预清理步骤并发,所以可能无法做到及时标记,所以需要一个stop the world 来完整标记所有对象。 并发清理(其他线程并行)目的是移除所有不用的对象,并且重新声明内存空间的归属等候将来使 并发重置(其他线程并行)CMS内部重置回收器状态,准备进入下一个并发回收周期 CMS的缺点: 对 CPU 资源敏感;需要有足够CPU资源 无法处理浮动垃圾,由于并发标记,用户线程在垃圾清理时依然会产生垃圾, 用户线程仍然在运行,必须预留出空间给用户线程使用,需要更大的堆空间。 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。 总结Serial/old 单线程 ParNew 多线程 serial多线程版本,可以和CMS搭配使用 Parallel scanvenge/old 多线程,吞吐量优先 CMS 真正并发的垃圾收集器 G1垃圾收集器RegionG1的内存结构和传统的内存空间划分有比较的不同。G1将内存划分成了多个大小相等的Region(1MB~32MB),Region逻辑上连续,物理内存地址不连续。同时每个Region被标记成E、S、O、H,分别表示Eden、Survivor、Old、Humongous。 其中E、S属于年轻代,O与H属于老年代。 Humongous表示大对象,一般当分配的对象大小大于等于region的一般是认为是H对象。 G1 内存分成小的region 每个reion有各自的分代属性,大小一致 分代不连续,搜索引用时要全盘扫描。 三色标记法 白:对象没有被标记到,标记阶段结束后,会被当做垃圾回收掉。 灰:对象被标记了,但是它的field还没有被标记或标记完。 黑:对象被标记了,且它的所有field也被标记完了。 并发标记过程中, Card Table: Card Table维护着所有的Card。Card Table的结构是一个字节数组,Card Table用这个数组映射着每一个Card Card中对象的引用发生改变时,Card在Card Table数组中对应的值被标记为dirty,就称这个Card被脏化了 所以Card Table其实就是映射着内存中的对象,Young GC的时候只需要扫描状态是dirty的card Remembered Set: RSet 每一个Region都有自己的RSet RSet里面记录了引用——就是其他Region中指向本Region中所有对象的所有引用,也就是谁引用了我的对象 RSet其实是一个Hash Table,Key是其他的Region的起始地址,Value是一个集合,里面的元素是Card Table 数组中的index,既Card对应的Index,映射到对象的Card地址。 比如A对象在regionA,B对象在regionB,且B.f = A,则在regionA的RSet中需要记录一对键值对,key是regionB的起始地址,Value的值能映射到B所在的Card的地址,所以要查找B对象,就可以通过RSet中记录的卡片来查找该对象 G1 垃圾回收过程: 初始标记 并发标记 最终标记 筛选回收 类的生命周期加载->验证->准备->解析->初始化->使用->卸载 加载,验证,准备,初始化,卸载这五个步骤的顺序是确定的,解析的时间是不确定的。 加载的时机 遇到new,getstatic,pusstatic,invokestaticnew关键字实例化 读取或者设置静态字段,调用静态方法 使用反射进行调用 初始化类,发现父类未初始化 虚拟机启动的主类 jdk7的动态语言支持,methodHandle解析结果为REF_getStatic,REF_putStatic,REF_invokeStatic,REF_newInvokeSpecial 接口设置默认方法,实现类初始化时要先初始化接口 加载 通过类的全限定名获取类的二进制字节流 将字节流中的静态存储结构转化为方法区的运行时数据结构 在内存中生成该类的Class对象作为方法去各种数据的访问入口 验证:文件格式验证、元数据验证、字节 码验证和符号引用验证 准备:为类定义的变量分配初始值,1.8以前在永久代里面分配,1.8之后,和Class对象一起放在堆中,final变量直接赋值 解析:解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程 初始化 执行类构造器()的过程,编译器将自动收集(按顺序)类变量的赋值语句和static语句块,父类必须在子类之前执行 类加载器 Boostrap ClassLoader java_home/lib下指定文件名中的类,如rt.jar,tool.jar 无法直接在代码中使用 extension ClassLoader java_home/lib/ext或者java.ext.dir指定目录下的类库 Application ClassLoader classPath下的所有类库 一般程序用的类加载器 双亲委派机制: 双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加 载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的 加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请 求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。 虚拟机字节码执行引擎栈帧 栈帧与方法相关联,方法的局部变量表、操作数栈、动态连接和方法返回地址","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"反射与代理","slug":"java/反射与代理","date":"2019-09-30T16:00:00.000Z","updated":"2021-02-18T09:00:19.767Z","comments":true,"path":"2019/10/01/java/反射与代理/","link":"","permalink":"http://renjiahui.cn/2019/10/01/java/%E5%8F%8D%E5%B0%84%E4%B8%8E%E4%BB%A3%E7%90%86/","excerpt":"","text":"反射反射机制的定义: 是在运行状态中,对于任意的一个类,都能够知道这个类的所有属性和方法,对任意一个对象都能够通过反射机制调用一个类的任意方法,这种动态获取类信息及动态调用类对象方法的功能称为java的反射机制。 反射的作用: 1、动态地创建类的实例,将类绑定到现有的对象中,或从现有的对象中获取类型。 2、应用程序需要在运行时从某个特定的程序集中载入一个特定的类 反射 一般使用 Class.forName()方法; jdk提供了三种方式获取一个对象的Class,就User user来说 1.user.getClass(),这个是Object类里面的方法 2.User.class属性,任何的数据类型,基本数据类型或者抽象数据类型,都可以通过这种方式获取类 3.Class.forName(“”),Class类提供了这样一个方法,让我们通过类名来获取到对象类 代理模式静态代理静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类 1234567891011121314151617181920212223242526272829/** * 接口 */public interface IUserDao { void save();}/** * 接口的实现类 */public class UserDao implements IUserDao { public void save() { System.out.println(\"----已经保存数据!----\"); }}/** * 代理对象,静态代理 */public class UserDaoProxy implements IUserDao{ //接收保存目标对象 private IUserDao target; public UserDaoProxy(IUserDao target){ this.target=target; } public void save() { System.out.println(\"开始事务...\"); target.save();//执行目标对象的方法 System.out.println(\"提交事务...\"); }} 静态代理总结:1.优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.2.缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护. 动态代理动态代理有以下特点:1.代理对象,不需要实现接口2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)3.动态代理也叫做:JDK代理,接口代理 JDK中生成代理对象的API代理类所在包:java.lang.reflect.ProxyJDK实现代理只需要使用newProxyInstance方法 1static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h ) 代码示例: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950/** * 创建动态代理对象 * 动态代理不需要实现接口,但是需要指定接口类型 */public class ProxyFactory{ //维护一个目标对象 private Object target; public ProxyFactory(Object target){ this.target=target; } //给目标对象生成代理对象 public Object getProxyInstance(){ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(\"开始事务2\"); //执行目标对象方法 Object returnValue = method.invoke(target, args); System.out.println(\"提交事务2\"); return returnValue; } } ); }}/** * 测试类 */public class App { public static void main(String[] args) { // 目标对象 IUserDao target = new UserDao(); // 原始的类型 class cn.itcast.b_dynamic.UserDao】 System.out.println(target.getClass()); // 给目标对象,创建代理对象 IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance(); // class $Proxy0 内存中动态生成的代理对象 System.out.println(proxy.getClass()); // 执行方法 proxy.save(); }} 动态代理的原理: jdk代理主要通过反射机制实现,在代理过程中,动态的创建代理对象的子类。 12//Proxy.newProxyInstance中调用生成代理类的逻辑 Class<?> cl = getProxyClass0(loader, intfs); Cglib代理 JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现. Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截) Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉. 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364/** * 目标对象,没有实现任何接口 */public class UserDao { public void save() { System.out.println(\"----已经保存数据!----\"); }}/** * Cglib子类代理工厂 * 对UserDao在内存中动态构建一个子类对象 */public class ProxyFactory implements MethodInterceptor{ //维护目标对象 private Object target; public ProxyFactory(Object target) { this.target = target; } //给目标对象创建一个代理对象 public Object getProxyInstance(){ //1.工具类 Enhancer en = new Enhancer(); //2.设置父类 en.setSuperclass(target.getClass()); //3.设置回调函数 en.setCallback(this); //4.创建子类(代理对象) return en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(\"开始事务...\"); //执行目标对象的方法 Object returnValue = method.invoke(target, args); System.out.println(\"提交事务...\"); return returnValue; }}/** * 测试类 */public class App { @Test public void test(){ //目标对象 UserDao target = new UserDao(); //代理对象 UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance(); //执行代理对象的方法 proxy.save(); }} java注解注解的概念,相当于标签,提供给编译器和工具使用 java元注解元注解的作用就是负责注解其他注解。 java中有四种元注解:@Retention、@Inherited、@Documented、@Target @Retention注解的保留位置(枚举RetentionPolicy),RetentionPolicy可选值: SOURCE:注解仅存在于源码中,在class字节码文件中不包含CLASS:默认的保留策略,注解在class字节码文件中存在,但运行时无法获得RUNTIME:注解在class字节码文件中存在,在运行时可以通过反射获取到 @Inherited声明子类可以继承此注解,如果一个类A使用此注解,则类A的子类也继承此注解 @Documented声明注解能够被javadoc等识别(下面自定义注解处会有例子做介绍,点击查看) @Target用来声明注解范围(枚举ElementType),ElementType可选值: TYPE:接口、类、枚举、注解FIELD:字段、枚举的常量METHOD:方法PARAMETER:方法参数CONSTRUCTOR:构造函数LOCAL_VARIABLE:局部变量ANNOTATION_TYPE:注解PACKAGE:包","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"算法","slug":"计算机基础/算法note","date":"2019-09-28T16:00:00.000Z","updated":"2021-01-11T19:19:19.186Z","comments":true,"path":"2019/09/29/计算机基础/算法note/","link":"","permalink":"http://renjiahui.cn/2019/09/29/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E7%AE%97%E6%B3%95note/","excerpt":"","text":"算法排序算法 shell排序思想:用步长分成不同的组进行插入排序 快排思想,设立划分区间,然后进行分治 冒泡:逐个交换 归并:分组然后合并排序 基数排序:对不同关键字依次进行排序 堆排序:建立大根堆,然后交换堆顶和堆低,调整堆,重复。 不稳定:快希选一堆(快些选一堆) 动态规划可以采用动态规划解决的问题: 具有最有子结构性质,即最优解包含的子问题的解也是最优解 无后效性,当前状态不受之后的影响 重叠子问题,子问题的解在下一阶段的决策会用到","categories":[],"tags":[{"name":"计算机基础","slug":"计算机基础","permalink":"http://renjiahui.cn/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/"}]},{"title":"Java单元测试","slug":"java/java单元测试","date":"2019-09-22T16:00:00.000Z","updated":"2021-01-11T19:22:48.897Z","comments":true,"path":"2019/09/23/java/java单元测试/","link":"","permalink":"http://renjiahui.cn/2019/09/23/java/java%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/","excerpt":"","text":"JunitJUnit4是一个易学易用的Java单元测试框架,一般我们在写完一段代码或一个方的时候,都要测试一下这段代码和这个方法的逻辑是不是正确,输入一定的数据,返回的数据是不是我们想要的结果,即我们在写单个业务代码针对结果进行测试。这时Junit就派上了大用场了。 在写完一个类或者方法后可以添加测试类进行测试 Junit中集中基本注解,是必须掌握的 @BeforeClass – 表示在类中的任意public static void方法执行之前执行@AfterClass – 表示在类中的任意public static void方法执行之后执行@Before – 表示在任意使用@Test注解标注的public void方法执行之前执行@After – 表示在任意使用@Test注解标注的public void方法执行之后执行@Test – 使用该注解标注的public void方法会表示为一个测试方法","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"数据库","slug":"计算机基础/Database","date":"2019-09-19T16:00:00.000Z","updated":"2021-01-13T07:00:43.708Z","comments":true,"path":"2019/09/20/计算机基础/Database/","link":"","permalink":"http://renjiahui.cn/2019/09/20/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/Database/","excerpt":"","text":"数据库语言DDL,DML,DQL,DCL 数据定义语言(Data Definition Language,DDL) 数据操作语言(Data Manipulation Language,DML) 数据查询语言(Data Query Language,DQL) 数据控制语言(Data Control Language,DCL) 数据库三范式:123第一范式:要求有主键,并且要求每一个字段原子性不可再分第二范式:要求所有非主键字段完全依赖主键,不能产生部分依赖第三范式:所有非主键字段和主键字段之间不能产生传递依赖 数据库基础1.主键是能确定一条记录的唯一标识,比如,一条记录包括身份正号,姓名,年龄。2.外键用于与另一张表的关联。是能确定另一张表记录的字段,用于保持数据的一致性 主键的设计原则: 对用户没有意义 单列,便于筛选和连接的操作 永远不要更新主键也不要包含动态变化的数据,如时间等 主键应由计算机自动生成 数据库字段约束条件: UNSIGNED :无符号,值从0开始,无负数ZEROFILL:零填充,当数据的显示长度不够的时候可以使用前补0的效果填充至指定长度,字段会自动添加UNSIGNEDNOT NULL:非空约束,表示该字段的值不能为空DEFAULT:表示如果插入数据时没有给该字段赋值,那么就使用默认值PRIMARY KEY:主键约束,表示唯一标识,不能为空,且一个表只能有一个主键。一般都是用来约束idAUTO_INCREMENT:自增长,只能用于数值列,而且配合索引使用,默认起始值从1开始,每次增长1UNIQUE KEY:唯一值,表示该字段下的值不能重复,null除外。比如身份证号是一人一号的,一般都会用这个进行约束FOREIGN KEY:外键约束,目的是为了保证数据的完成性和唯一性,以及实现一对一或一对多关系 mysql架构和简单流程基本架构客户端:负责连接处理,认证等功能 核心服务:负责 查询解析,优化,缓存,一些内置函数,还有存储过程,触发器,视图等。 存储引擎:负责数据的存储,更新和检索,表的创建。 MySQL常用数据类型: 数据类型 数据类型 字节数 带符号最小值 带符号最大值 不带符号最小值 不带符号最大值 TINYINT 1 -128 127 0 255 SMALLINT 2 -32768 32767 0 65535 MEDIUMINT 3 -8388608 8388607 0 16777215 INT 4 -2147483648 2147483647 0 4294967295 BIGINT 8 -9223372036854775808 9223372036854775807 0 18446744073709551616 字符串类型 日期时间类型 存储引擎5.5之前默认MyISAM,之后默认InnoDB InnoDB:事务,行锁,外键约束,灾难恢复。MVCC(高并发事务) MyISAM: 会保存数据的行数。 两种引擎的适用场景: MyISAM 适合查询非常频繁的,不需要事务 InnoDB 社和更新插入频繁,需要可靠性,事务要求。 ACID的实现事务的 ACID 是通过 InnoDB 日志和锁来保证。 事务的隔离性是通过数据库锁的机制实现的, 持久性通过 Redo Log(重做日志)来实现, 原子性和一致性通过 Undo Log 来实现。 Undo Log 的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 Undo Log)。然后进行数据的修改。 如果出现了错误或者用户执行了 Rollback 语句,系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。 和 Undo Log 相反,Redo Log 记录的是新数据的备份。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。 当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到***的状态。 MVCC(全称)多版本并发控制 乐观锁的一种实现方式,广泛应用于数据库技术中,不仅mysql,PostgreSQL等也有该技术。 MVCC的实现,通过保存数据在某个时间点的快照来实现的。这意味着一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图。根据事务开始的时间不同,同时也意味着在同一个时刻不同事务看到的相同表里的数据可能是不同的。 用与实现读已提交和可重复度两个隔离级别 InnoDB实现原理对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列(row_id并不是必要的,我们创建的表中有主键或者非NULL唯一键时都不会包含row_id列): trx_id:每次对某条聚簇索引记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。 roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。 每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表。 ReadView作用:判断版本链中的哪个版本是当前事务可见的。 数据库会维护一个当前活跃的事务的列表(未提交) 事务四大特性1234567原子性,要么执行,要么不执行,要么执行成功 要么全部回滚 隔离性,所有操作全部执行完以前其它会话不能看到过程,保证并发时的数据修改正确一致性,事务前后,数据总额一致,事务前后的意义一致,数据合法性检查持久性,一旦事务提交,对数据的改变就是永久的,持久化功能。 可能发生的问题: 12345更新丢失:两个事务都对一个数据进行修改,T1先修改,T2覆盖了T1的修改,造成更新丢失。脏读:事务B读取事务A还没有提交的数据不可重复读:一次事务内两次读取同一个数据不一致幻读:事务A修改了数据,事务B也修改了数据,这时在事务A看来,明明修改了数据,咋不一样//注:不可重复读偏向的是修改,而幻读侧重的是插入操作 事务的隔离级别: 12345(从高到低)Serializable (串行化):可避免脏读、不可重复读、幻读的发生。Repeatable read (可重复读):可避免脏读、不可重复读的发生。Read committed (读已提交):可避免脏读的发生。Read uncommitted (读未提交):最低级别,任何情况都无法保证。 InnoDB 默认的事务隔离级别是可重复读(repeatable-read)。 不同隔离级别的实现: 读未提交时: 事务在读数据的时候并未对数据加锁。 事务在修改数据的时候只对数据增加行级共享锁。 读已提交时: 事务对当前被读取的数据加 行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。 可重复读时: 事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加 行级共享锁,直到事务结束才释放; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。 串行化时: 事务在读取数据时,必须先对其加 表级共享锁 ,直到事务结束才释放; 事务在更新数据时,必须先对其加 表级排他锁 ,直到事务结束才释放。 关于MVCC和幻读的一些理解(个人看法,待考证) 之前在在使用mvcc的情况下,RR隔离级别已经可以解决幻读的问题了,为什么还要用序列化解决幻读问题。 首先考虑这样一个场景,有两个事务,事务A 插入一条数据,事务B先查询是否有这条数据,然后再执行插入,那么在RR隔离级别下,B在查询时未发现有数据,此时A事务提交,数据插入,这时B再执行插入时,发现无法插入了,产生了幻读(明明查询没有数据,但是无法插入,像是幻觉。。。。),这时候RR已经无法解决了,只能通过序列化的形式来解决这个问题。 注:也有可能A事务为提交,但是由于写锁的存在,B还是无法执行插入。 锁机制与InnoDB锁算法 MyISAM采用表级锁(table-level locking)。 InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁 表级锁: MySQL中锁定 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。 行级锁: MySQL中锁定 粒度最小 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。 锁算法: Record Lock: 对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项; Gap Lock: 对索引项之间的“间隙”加锁,锁定记录的范围(比如对第一条记录前的间隙或最后一条将记录后的间隙加锁),不包含索引项本身。其他事务不能在锁范围内插入数据,这样就防止了别的事务新增幻影行。 Next-key Lock: 锁定索引项本身和索引范围。即Record Lock和Gap Lock的结合。可解决幻读问题。 读写锁详解: 共享锁和排他锁 意向锁: 在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。 意向锁在原来的 X/S 锁之上引入了 IX/IS,IX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定: 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁; 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。 通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。 当前读和快照读: 当前读:加锁读,读取记录的最新版本,会加锁保证其他并发事务不能修改当前记录,直至获取锁的事务释放锁; 显式加锁的读操作与插入/更新/删除等写操作。 快照读:不加锁读,读取记录的快照版本,采用MVCC实现,RR隔离级别下 不显式加锁的select都采用快照读。 数据库索引MySQL索引使用的数据结构主要有BTree索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址。 索引类型聚簇索引:索引和数据存放在一起的为聚簇索引,否则为非聚簇索引,Innodb默认以主键创建聚簇索引。 唯一索引:建立索引的键值唯一表示一行数据,可以为空。 普通索引:就普通的索引 联合索引:(组合索引)多个键值组合在一起建立索引。 组合索引最左前缀原则 当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+树是按照从左到右的顺序来建立搜索树的,b+树会优先比较最左的数据项来确定下一步的所搜方向 B树和B+树区别B+树只有叶子节点包含数据,且所有叶子节点由链表连接 存储过程存储过程是一组 Transact-SQL 语句,它们只需编译一次,以后即可多次执行。因为 Transact-SQL 语句不需要重新编译,所以执行存储过程可以提高性能。 触发器是一种特殊的存储过程,不由用户直接调用。创建触发器时,将其定义为在对特定表或列进行特定类型的数据修改时激发。 mysql主从复制主从复制实现基本原理? 1、复制是MySQL自带的一项功能,允许服务器将更改从一个服务器的一个实例复制到另一个实例。 2、主服务器将所有数据和结构更改记录到二进制日志中。 3、从属服务器从主服务器请求该二进制日志并在本地应用其内容。即通过把主库的binlog传送到从库,从新解析应用到从库。 MySQL支持的复制类型 (1)基于语句的复制:在主服务器上执行的SQL语句,在从服务器上执行同样的语句。MySQL默认采用基于语句的复制,效率比较高。 一旦发现没法精确复制时,会自动选择基于行的复制。 (2)基于行的复制:把改变的内容复制过去,而不是把命令在从服务器上执行一遍。从MySQL5.0开始支持。 (3)混合类型复制:默认采用基于语句的复制,一旦发现基于语句无法精确复制时,就会采用基于行的复制。 主从复制半同步模式: master会等待至少一个slave 写入relay log 后返回ack后才会提交事务。在超时后会切换异步,在收到一个ack后再切回同步。 一些思考: 主从复制带来的延迟导致的脏读问题: 一般来说,主库作为写库,从库作为读库来进行读写分离,那么由于主从复制存在一定的延迟,在高并发下,读从库时会导致无法读到最新的数据。 主库事务提交后可以先写入redis过期时间设置为同步需要的时间(需要代码的对应逻辑) 分页,分库,分表mysql 分页的操作可以采用 limit 1select * from student limit(curPage-1)*pageSize,pageSize; curPage 为当前页,pageSize为每页行数 也可以使用offset进行索引(mysql5.0开始支持) 1SELECT * FROM admin LIMIT columeNum OFFSET startNum; offset表示开端的索引,从0开始 读写分离可以解决高并发的读问题,分库分表解决数据量过大的问题 切分方式: 垂直切分:(列切分)首先考虑垂直切分,从业务上可以拆分成不同的库,或者将一张大表拆分成多个表 水平切分:(行切分)如果一张表的行数据很大,那么在查询时会带来性能的下降(mysql大约500万条数据?待考证) 可以考虑根据某些列进行分库分表,例如根据主键或者ID进行分表,查询时可以根据键值确定对应的库和表。选取的键应该可以承载大数据,且分布均匀,分表后各个表大小相当,且较容易建立键中计算库和表的规则。 缓存机制就正常的缓存机制,没有很特殊的地方,缓存查询的结果。 数据库慢查询的优化 单次查询改为批量处理 分页改流式处理 减少返回的不必要的字段 binlog,redo log和undo log的区别redo log 和undo log 是innoDB引擎 Undo + Redo事务的简化过程 假设有A、B两个数据,值分别为1,2. A.事务开始. B.记录A=1到undo log. C.修改A=3. D.记录A=3到redo log. E.记录B=2到undo log. F.修改B=4. G.记录B=4到redo log. H.将redo log写入磁盘。 I.事务提交 binlog 是mysql Service层的日志,记录每次操作的","categories":[],"tags":[{"name":"数据库","slug":"数据库","permalink":"http://renjiahui.cn/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"}]},{"title":"HashMap","slug":"java/HashMap","date":"2019-08-19T16:00:00.000Z","updated":"2021-01-11T19:23:01.461Z","comments":true,"path":"2019/08/20/java/HashMap/","link":"","permalink":"http://renjiahui.cn/2019/08/20/java/HashMap/","excerpt":"","text":"HashMap简介HashMap 主要用来存放键值对,它基于哈希表的Map接口实现,是常用的Java集合之一。 JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。 HashMap分析hash方法主要对key值的hashcode进行扰动减少碰撞,得到hash值,然后通过(n-1)&hash 得到对象存放的位置(数组位置,n为数组长度),然后判断数组当前位置的key值与要存入的是否相同,若相同,则直接覆盖,否则,采用拉链法。 为什么高16位和低16位异或,之后的计算位置时只采用最低的几位,将高位参与运算,减少碰撞的概率。 123456789//JDK1.8hash实现,右移16位一次static final int hash(Object key) { int h; // key.hashCode():返回散列值也就是hashcode // ^ :按位异或 // >>>:无符号右移,忽略符号位,空位都以0补齐 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //高16位和低16位相异或} 类属性12345678910111213141516171819202122232425262728public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 序列号 private static final long serialVersionUID = 362498820763181265L; // 默认的初始容量是16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; // 默认的填充因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 当桶(bucket)上的结点数大于这个值时会转成红黑树 static final int TREEIFY_THRESHOLD = 8; // 当桶(bucket)上的结点数小于这个值时树转链表 static final int UNTREEIFY_THRESHOLD = 6; // 桶中结构转化为红黑树对应的table的最小大小 static final int MIN_TREEIFY_CAPACITY = 64; // 存储元素的数组,总是2的幂次倍 transient Node<k,v>[] table; // 存放具体元素的集 transient Set<map.entry<k,v>> entrySet; // 存放元素的个数,注意这个不等于数组的长度。 transient int size; // 每次扩容和更改map结构的计数器 transient int modCount; // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容 int threshold; // 加载因子 final float loadFactor;} CAPACITY 采用 (n - 1) & hash来定位,位运算提高计算的效率,等同于取余。 取余后均匀分布,减少hash冲突。 loadFactor加载因子 loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor越小,也就是趋近于0,数组中存放的数据(entry)也就越少,也就越稀疏。 loadFactor太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor的默认值为0.75f是官方给出的一个比较好的临界值。 给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。 threshold threshold = capacity * loadFactor,当Size>=threshold的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是 衡量数组是否需要扩增的一个标准。 Node类源码123456789101112131415161718192021222324252627282930313233343536373839// 继承自 Map.Entry<K,V>static class Node<K,V> implements Map.Entry<K,V> { final int hash;// 哈希值,存放元素到hashmap中时用来与其他元素hash值比较 final K key;//键 V value;//值 // 指向下一个节点 Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + \"=\" + value; } // 重写hashCode()方法 public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } // 重写 equals() 方法 public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; }} 可以看到HashMap重写了hashcode()方法和equals()方法 树节点源码(转换为红黑树时使用)12345678910111213141516static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // 父 TreeNode<K,V> left; // 左 TreeNode<K,V> right; // 右 TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; // 判断颜色 TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } // 返回根节点 final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } 构造方法123456789101112131415161718192021222324252627// 默认构造函数。public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}// 包含另一个“Map”的构造函数public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false);//下面会分析到这个方法}// 指定“容量大小”的构造函数public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR);}// 指定“容量大小”和“加载因子”的构造函数public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException(\"Illegal initial capacity: \" + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException(\"Illegal load factor: \" + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity);} putMapEntries方法:12345678910111213141516171819202122232425//批量添加元素 从另一个map里添加final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { // 判断table是否已经初始化 if (table == null) { // pre-size // 未初始化,s为m的实际元素个数 float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); // 计算得到的t大于阈值,则初始化阈值 if (t > threshold) threshold = tableSizeFor(t); } // 已初始化,并且m元素个数大于阈值,进行扩容处理 else if (s > threshold) resize(); // 将m中的所有元素添加至HashMap中 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } }} put方法put方法调用putVal方法来添加元素,putVal方法的流程如下 ①如果定位到的数组位置没有元素 就直接插入。 ②如果定位到的数组位置有元素就和要插入的key比较,如果key相同就直接覆盖,如果key不相同,就判断p是否是一个树节点,如果是就调用e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)。 get方法12345678910111213141516171819202122232425262728293031public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value;}final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // 数组元素相等 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; // 桶中不止一个节点 if ((e = first.next) != null) { // 在树中get if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); // 在链表中get do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null;} Resize方法 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { // 超过最大值就不再扩充了,就只好随你碰撞去吧 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 没超过最大值,就扩充为原来的2倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 计算新的resize上限 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({\"rawtypes\",\"unchecked\"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { // 把每个bucket都移动到新的buckets中 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; // 原索引 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } // 原索引+oldCap else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); // 原索引放到bucket里 if (loTail != null) { loTail.next = null; newTab[j] = loHead; } // 原索引+oldCap放到bucket里 if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab;} LinkedHashMap本质上,LinkedHashMap = HashMap + 双向链表 比HashMap多维护一个双向链表用来表示每一个Entry的插入顺序(默认)或者访问顺序。 currentHashMap问题:hashmap的死循环问题 头插法造成的问题,1.8的改进","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"计算机网络相关知识","slug":"计算机基础/网络","date":"2019-08-19T16:00:00.000Z","updated":"2021-01-11T19:19:59.922Z","comments":true,"path":"2019/08/20/计算机基础/网络/","link":"","permalink":"http://renjiahui.cn/2019/08/20/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E7%BD%91%E7%BB%9C/","excerpt":"","text":"五层模型及对应的协议:应用层:http.SMTP 数据单元 报文 传输层:tcp,udp 网络层:IP,相关的路由协议 数据链路层:PPP and 以太网 物理层:相关的物理协议 TCP连接建立与断开过程图: TCP建立的三次握手: 1、发送端发送一个SYN报文段(SYN位被置位),SYN中包含TCP目的端口和发送端的初始序列号(图中ISN(c)),同时携带着TCP选项数据。 2、接收端收到发送端连接请求后,接收端发送自己SYN报文段(包含ISN(s)),同时对发送端的SYN进行确认,如前所述,接收端发送的ACK是ISN(c)+1。此时ACK位与SYN位都被置位。接收端发送SYN+ACK到发送端。 3、发送端接收到接收端的SYN+ACK数据后,对ISN(s)进行确认,发送ACK为ISN(s)+1的报文段给接收端。 TCP断开的四次握手: 1、TCP协议规定通过发送一个FIN段(FIN被置位)来发起关闭操作,图3中发送端发送FIN段给接收端,告知它数据已发送完毕,请求断开TCP连接。同时FIN报文段还包含着对最近收到的数据进行ACK。 2、接收端接收端FIN报文段后,对FIN进行确认,发送ACK=k+1给发送端。 3、接收端将连接关闭发送给上层应用程序,由应用程序发起连接关闭操作。此时接收端由被动关闭连接壮成主动,并发送FIN报文段给发送端。报文的序列号为L(这里也可看出上一步骤中发送ACK序列号也为L,因为ACK不占用序列号,所以这里的FIN的序列号也为L)。 4、发送端接收到FIN后,发送回ACK给接收端后,TCP连接终止。如果FIN丢失,发送FIN的那端需要重新发送FIN,知道接收到ACK为止。 为什么要三次握手四次挥手三次握手:第一次,接收方确认自己的收消息没问题,第二次,发送发确认自己的发送消息和接受消息通路没有问题,第三次,接收方确认自己的发送通路没有问题。 状态的变化:客户端发送第一次消息时,进入到准备发送阶段,服务端接收到第一条消息后进入到准备接受阶段,并发送第二条消息,客户端接收到第二条消息后,正式建立连接,并发送确认消息,服务端接收到确认消息后,也正式建立连接,此时通道建立完成。 滑动窗口和流量控制为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。 TCP的拥塞控制采用了四种算法,即 慢开始 、 拥塞避免 、快重传 和 快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。 慢开始: 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍。 拥塞避免: 拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大,即每经过一个往返时间RTT就把发送放的cwnd加1. 快重传与快恢复: 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。 当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。 拥塞控制示意图: CDN普通访问: ①用户在自己的浏览器中输入要访问的网站域名。 ②浏览器向本地DNS服务器请求对该域名的解析。 ③本地DNS服务器中如果缓存有这个域名的解析结果,则直接响应用户的解析请求。 ④本地DNS服务器中如果没有关于这个域名的解析结果的缓存,则以递归方式向整个DNS系统请求解析,获得应答后将结果反馈给浏览器。 ⑤浏览器得到域名解析结果,就是该域名相应的服务设备的IP地址。 ⑥浏览器向服务器请求内容。 ⑦服务器将用户请求内容传送给浏览器。 使用CDN的访问过程 ①当用户点击网站页面上的内容URL,经过本地DNS系统解析,DNS系统会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器。 ②CDN的DNS服务器将CDN的全局负载均衡设备IP地址返回用户。 ③用户向CDN的全局负载均衡设备发起内容URL访问请求。 ④CDN全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求。 ⑤区域负载均衡设备会为用户选择一台合适的缓存服务器提供服务,选择的依据包括:根据用户IP地址,判断哪一台服务器距用户最近;根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。基于以上这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的IP地址。 ⑥全局负载均衡设备把服务器的IP地址返回给用户。 ⑦用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端。如果这台缓存服务器上并没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地。 DNS服务器根据用户IP地址,将域名解析成相应节点的缓存服务器IP地址,实现用户就近访问。使用CDN服务的网站,只需将其域名解析权交给CDN的GSLB设备,将需要分发的内容注入CDN,就可以实现内容加速了。 DNSDNS是应用层协议,主要是将域名转换为ip地址 具体的查找过程和策略可以分为下面这几步: 先检查hosts,然后本地域名解析的缓存,再到tcp设置的DNS服务器,本地DNS请求根DNS,根DNS返回顶级域名服务器,顶级域名服务器若无法解析,则交给下一级的DNS服务器,重复操作,知道找到解析的主机为止。 (1)在浏览器中输入www.google.cn域名,操作系统会先检查自己本地的hosts文件是否有这个网址映射关系,如果有,就先调用这个IP地址映射,完成域名解析。(2)如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存,是否有这个网址映射关系,如果有,直接返回,完成域名解析。(3)如果hosts与本地DNS解析器缓存都没有相应的网址映射关系,首先会找TCP/IP参数中设置的首选DNS服务器,在此我们叫它本地DNS服务器,此服务器收到查询时,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。(4)如果要查询的域名,不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析,此解析不具有权威性。(5)如果本地DNS服务器本地区域文件与缓存解析都失效,则根据本地DNS服务器的设置(是否设置转发器)进行查询,如果未用转发模式,本地DNS就把请求发至13台根DNS,根DNS服务器收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP。本地DNS服务器收到IP信息后,将会联系负责.com域的这台服务器。这台负责.com域的服务器收到请求后,如果自己无法解析,它就会找一个管理.com域的下一级DNS服务器地址(google.com)给本地DNS服务器。当本地DNS服务器收到这个地址后,就会找google.com域服务器,重复上面的动作,进行查询,直至找到www.google.com主机。(6)如果用的是转发模式,此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。不管是本地DNS服务器用是是转发,还是根提示,最后都是把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机。 Socket,tcp和HTTP的区别 socket是一个抽象层 浏览器访问网页过程详解:1.DNS解析,详见DNS过程 2.通过http协议交换数据 3.http转交给传输层,tcp建立连接,传输数据,断开连接。 4.tcp的报文会交给网络层程序,在网络上传输,路由选择 5.服务器接收到ip报文后,向上层层解析,一直到服务端的http,然后将相应结果通过http发送给客户端。","categories":[],"tags":[{"name":"计算机基础","slug":"计算机基础","permalink":"http://renjiahui.cn/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/"}]},{"title":"笔面试记录","slug":"其他/笔面试记录","date":"2019-07-28T16:00:00.000Z","updated":"2021-01-11T19:06:33.488Z","comments":true,"path":"2019/07/29/其他/笔面试记录/","link":"","permalink":"http://renjiahui.cn/2019/07/29/%E5%85%B6%E4%BB%96/%E7%AC%94%E9%9D%A2%E8%AF%95%E8%AE%B0%E5%BD%95/","excerpt":"","text":"此篇博文主要用于记录,在准备秋招的笔试和面试过程中,遇到了一些自己认为该注意的点,不定期更新~。 一些知识点的记录:continue 结束本次循环,进入下一轮 break 结束循环 “\\n”换行 “\\r”回车 “%n” 平台无关换行 import问题在同一个包下,一个类访问另一个类的静态成员变量无需import 只要直接 类名.成员名即可 ceil()和floor()区别ceil()对整形变量向左取整,返回类型为double型。不小于x的最大整数,如ceil(1.5) =2 floor()对整形变量向左取整,返回类型为double型。不大于x的最大整数,如floor(1.5) = 1; round(x) = Math.floor(x+0.5) 四舍五入 Stringbuffer 和Stringbuilderbuffer 线程安全,先提出,builder线程不安全,后提出为了改善效率。 日期和时间获取时间Date();获取日期Calendar().getInstance(); java 引用传递只存在值传递,只存在值传递!!!基础类型传值,引用了类型虽然传引用,但是传递的是传入的对象的一个拷贝的引用。修改并不影响原来对象。 == 和equals== 基本数据类型比较的是值 引用类型比较的是引用的对象的地址 equals 默认比较的是对象的地址(String重写了equals方法) String类的equals方法步骤: 1.若A==B 即是同一个String对象 返回true 2.若对比对象是String类型则继续,否则返回false 3.判断A、B长度是否一样,不一样的话返回false 4。逐个字符比较,若有不相等字符,返回false hashcode 两个对象equals 则hashcode一定相等,反之则不一定相等 若重写了equals的方法,则有必要重写hashcode方法,确保equals为true的两个对象的hashcode返回值相同。 java 内部类 成员内部类等同与成员变量 局部内部类等同与局部变量,没有修饰符,内部可访问。 匿名内部类在实现父类或者接口时提供相应的对象而不许需要增加额外的方法。 静态内部类静态成员变量,可以新建多个类,每次new都是不同的对象,但是和外部类之间没有强耦合,不用担心OOM 其他:编译时得到两个class文件一个为外部类 Outter.class文件 另一个为内部类的字节码文件Outter$Inner.class 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。 函数式接口可以有方法的默认实现,也可以与静态的方法,也可以定义java.lang.Object 的public方法 可以用lambda表示一个函数式接口的实现 常用的函数式接口 java.lang.Runnable, java.awt.event.ActionListener, java.util.Comparator, java.util.concurrent.Callable java.util.function包下的接口,如Consumer、Predicate、Supplier等 函数式接口 参数类型 返回类型 用途 Consumer(消费型接口) T void 对类型为T的对象应用操作。void accept(T t) Supplier(供给型接口) 无 T 返回类型为T的对象。 T get(); Function<T, R>(函数型接口) T R 对类型为T的对象应用操作并返回R类型的对象。R apply(T t); Predicate(断言型接口) T boolean 确定类型为T的对象是否满足约束。boolean test(T t); 接口和抽象类的区别是什么? 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。 接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。(接口类中只能定义public static final常量) 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过extends关键字扩展多个接口。 接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)。 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。 在 Java 中定义一个不做事且没有参数的构造方法的作用Java 程序在执行子类的构造方法之前,如果没有用 super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super()来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。 构造方法有哪些特性? 名字与类名相同。 没有返回值,但不能用void声明构造函数。 生成类的对象时自动执行,无需调用。 == 与 equals(重要)== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。 equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。 List数组相互转换java steam 12345Integer [] myArray = { 1, 2, 3 };List myList = Arrays.stream(myArray).collect(Collectors.toList());//基本类型也可以实现转换(依赖boxed的装箱操作)int [] myArray2 = { 1, 2, 3 };List myList = Arrays.stream(myArray2).boxed().collect(Collectons.toList()); Guava 12List<String> il = ImmutableList.of(\"string\", \"elements\"); // from varrayList<String> il = ImmutableList.copyOf(aStringArray); // from array Collection.toArray()方法使用的坑&如何反转数组该方法是一个泛型方法:<T> T[] toArray(T[] a); 如果toArray方法中没有传递任何参数的话返回的是Object类型数组。 123456String [] s= new String[]{ \"dog\", \"lazy\", \"a\", \"over\", \"jumps\", \"fox\", \"brown\", \"quick\", \"A\"};List<String> list = Arrays.asList(s);Collections.reverse(list);s=list.toArray(new String[0]);//没有指定类型的话会报错 由于JVM优化,new String[0]作为Collection.toArray()方法的参数现在使用更好,new String[0]就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型。 不要在 foreach 循环里进行元素的 remove/add 操作如果要进行remove操作,可以调用迭代器的 remove方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身remove/add方法,迭代器都将抛出一个ConcurrentModificationException,这就是单线程状态下产生的 fail-fast 机制。 fail-fast 机制 :多个线程对 fail-fast 集合进行修改的时,可能会抛出ConcurrentModificationException,单线程下也会出现这种情况,上面已经提到过。 String类的Intern方法string的两种构造方式: 直接使用“”进行构造,字符串对象直接在常量池中创建(jdk 1.7 后常量池移到了堆中,1.6时常量池和方法区在一起) 使用new String()进行对象的创建,直接在堆中构建一个新的对象 Intern方法: intern方法可以看成返回常量池中该字符串对象的引用。如果没有该字符串对象就把这个对象(或引用)加到常量池。 新建一个类的方式 new 一个对象 调用clone()方法 使用反射构造一个对象 反序列化一个对象 java和C++区别java内存管理,C++指针访问,需要自己释放内存 java单继承,C++多继承 构造函数Constructor重载和重写问题可以被重载,即一个类中可以有多个构造器 不可以被重写,子类无法继承父类的构造函数和私有属性吗,(私有方法可以继承不能重写) 重载和重写的区别 重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。 重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。 Java 面向对象编程三大特性: 封装 继承 多态封装封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。 继承继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。 关于继承如下 3 点请记住: 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 子类可以用自己的方式实现父类的方法。(override重写)。 多态所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。 在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法) 构造方法有哪些特性? 名字与类名相同。 没有返回值,但不能用void声明构造函数。 生成类的对象时自动执行,无需调用。","categories":[],"tags":[{"name":"Java 面试","slug":"Java-面试","permalink":"http://renjiahui.cn/tags/Java-%E9%9D%A2%E8%AF%95/"}]},{"title":"javaIO","slug":"java/JavaIO总结","date":"2019-07-09T16:00:00.000Z","updated":"2021-02-18T08:57:13.646Z","comments":true,"path":"2019/07/10/java/JavaIO总结/","link":"","permalink":"http://renjiahui.cn/2019/07/10/java/JavaIO%E6%80%BB%E7%BB%93/","excerpt":"","text":"Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。 BIO、NIO、AIO的区别 BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。(同步、阻塞) NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。(同步、非阻塞) AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。(异步,阻塞) IO的类型 InputStream、OutputStream 基于字节操作的 IO Writer、Reader 基于字符操作的 IO File 基于磁盘操作的 IO Socket 基于网络操作的 IO Java IO NIO 即 Java New IO 是1个全新的、 JDK 1.4后提供的 IO API 提供了与标准IO不同的IO工作方式 可替代 标准Java IO 的IO API 主要组件: Channel :相当于IO中的stream,但是Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。 FileChannel:作用于IO文件流 DatagramChannel:作用于UDP协议 SocketChannel:作用于TCP协议 ServerSocketChannel:作用于TCP协议 Buffer:缓冲区,用在线程和channel之间,来缓存数据 Selector: 选择器是NIO的核心,它是channel的管理者通过执行select()阻塞方法,监听是否有channel准备好,一旦有数据可读,此方法的返回值是SelectionKey的数量 NIO代码示例 服务端: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990package cn.blog.test.NioTest;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.*;import java.nio.charset.Charset;import java.util.Iterator;import java.util.Set;public class MyNioServer { private Selector selector; //创建一个选择器 private final static int port = 8686; private final static int BUF_SIZE = 10240; private void initServer() throws IOException { //创建通道管理器对象selector this.selector=Selector.open(); //创建一个通道对象channel ServerSocketChannel channel = ServerSocketChannel.open(); channel.configureBlocking(false); //将通道设置为非阻塞 channel.socket().bind(new InetSocketAddress(port)); //将通道绑定在8686端口 //将上述的通道管理器和通道绑定,并为该通道注册OP_ACCEPT事件 //注册事件后,当该事件到达时,selector.select()会返回(一个key),如果该事件没到达selector.select()会一直阻塞 SelectionKey selectionKey = channel.register(selector,SelectionKey.OP_ACCEPT); while (true){ //轮询 selector.select(); //这是一个阻塞方法,一直等待直到有数据可读,返回值是key的数量(可以有多个) Set keys = selector.selectedKeys(); //如果channel有数据了,将生成的key访入keys集合中 Iterator iterator = keys.iterator(); //得到这个keys集合的迭代器 while (iterator.hasNext()){ //使用迭代器遍历集合 SelectionKey key = (SelectionKey) iterator.next(); //得到集合中的一个key实例 iterator.remove(); //拿到当前key实例之后记得在迭代器中将这个元素删除,非常重要,否则会出错 if (key.isAcceptable()){ //判断当前key所代表的channel是否在Acceptable状态,如果是就进行接收 doAccept(key); }else if (key.isReadable()){ doRead(key); }else if (key.isWritable() && key.isValid()){ doWrite(key); }else if (key.isConnectable()){ System.out.println(\"连接成功!\"); } } } } public void doAccept(SelectionKey key) throws IOException { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); System.out.println(\"ServerSocketChannel正在循环监听\"); SocketChannel clientChannel = serverChannel.accept(); clientChannel.configureBlocking(false); clientChannel.register(key.selector(),SelectionKey.OP_READ); } public void doRead(SelectionKey key) throws IOException { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE); long bytesRead = clientChannel.read(byteBuffer); while (bytesRead>0){ byteBuffer.flip(); byte[] data = byteBuffer.array(); String info = new String(data).trim(); System.out.println(\"从客户端发送过来的消息是:\"+info); byteBuffer.clear(); bytesRead = clientChannel.read(byteBuffer); } if (bytesRead==-1){ clientChannel.close(); } } public void doWrite(SelectionKey key) throws IOException { ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE); byteBuffer.flip(); SocketChannel clientChannel = (SocketChannel) key.channel(); while (byteBuffer.hasRemaining()){ clientChannel.write(byteBuffer); } byteBuffer.compact(); } public static void main(String[] args) throws IOException { MyNioServer myNioServer = new MyNioServer(); myNioServer.initServer(); }} 客户端代码: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768package cn.blog.test.NioTest;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import java.util.Iterator;public class MyNioClient { private Selector selector; //创建一个选择器 private final static int port = 8686; private final static int BUF_SIZE = 10240; private static ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE); private void initClient() throws IOException { this.selector = Selector.open(); SocketChannel clientChannel = SocketChannel.open(); clientChannel.configureBlocking(false); clientChannel.connect(new InetSocketAddress(port)); clientChannel.register(selector, SelectionKey.OP_CONNECT); while (true){ selector.select(); Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()){ SelectionKey key = iterator.next(); iterator.remove(); if (key.isConnectable()){ doConnect(key); }else if (key.isReadable()){ doRead(key); } } } } public void doConnect(SelectionKey key) throws IOException { SocketChannel clientChannel = (SocketChannel) key.channel(); if (clientChannel.isConnectionPending()){ clientChannel.finishConnect(); } clientChannel.configureBlocking(false); String info = \"服务端你好!!\"; byteBuffer.clear(); byteBuffer.put(info.getBytes(\"UTF-8\")); byteBuffer.flip(); clientChannel.write(byteBuffer); //clientChannel.register(key.selector(),SelectionKey.OP_READ); clientChannel.close(); } public void doRead(SelectionKey key) throws IOException { SocketChannel clientChannel = (SocketChannel) key.channel(); clientChannel.read(byteBuffer); byte[] data = byteBuffer.array(); String msg = new String(data).trim(); System.out.println(\"服务端发送消息:\"+msg); clientChannel.close(); key.selector().close(); } public static void main(String[] args) throws IOException { MyNioClient myNioClient = new MyNioClient(); myNioClient.initClient(); }} 小结IO 是同步阻塞的,一个线程只能处理一个链路,可以使用线程池来处理,但本质上还是同步的,当前线程只能阻塞到IO准备好为止,才能进行IO NIO Non-blocking IO/New IO 同步非阻塞, 面向流和面向缓冲区:NIO是面向缓冲区的,将所有数据读到或者写到缓冲区再进行操作。 通道: 通道是双向的,既可以写也可以读,不需要为输入输出单独建流。 选择器:NIO通过选择器来监控多个通道的状态,这样无需为每一个单独的连接建立一个线程,可以实现单线程管理多个通道,提高系统效率。","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"maven","slug":"工具/maven","date":"2019-06-29T16:00:00.000Z","updated":"2021-01-11T19:19:46.493Z","comments":true,"path":"2019/06/30/工具/maven/","link":"","permalink":"http://renjiahui.cn/2019/06/30/%E5%B7%A5%E5%85%B7/maven/","excerpt":"","text":"1、POMpom文件是maven工程的基本工作单元,在项目的根目录下(模块项目则在模块的根目录下) POM 中可以指定以下配置: 项目依赖 插件 执行目标 项目构建 profile 项目版本 项目开发者列表 相关邮件列表信息 123456789101112131415161718<project xmlns = \"http://maven.apache.org/POM/4.0.0\" xmlns:xsi = \"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation = \"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\"> <!-- 模型版本 --> <modelVersion>4.0.0</modelVersion> <!-- 公司或者组织的唯一标志,并且配置时生成的路径也是由此生成, 如com.companyname.project-group,maven会将该项目打成的jar包放本地路径:/com/companyname/project-group --> <groupId>com.companyname.project-group</groupId> <!-- 项目的唯一ID,一个groupId下面可能多个项目,就是靠artifactId来区分的 --> <artifactId>project</artifactId> <!-- 版本号 --> <version>1.0</version></project>1、Snapshot 版本代表不稳定、尚处于开发中的版本。2、Release 版本则代表稳定的版本。 maven 生命周期 验证 validate 验证项目 验证项目是否正确且所有必须信息是可用的 编译 compile 执行编译 源代码编译在此阶段完成 测试 Test 测试 使用适当的单元测试框架(例如JUnit)运行测试。 包装 package 打包 创建JAR/WAR包如在 pom.xml 中定义提及的包 检查 verify 检查 对集成测试的结果进行检查,以保证质量达标 安装 install 安装 安装打包的项目到本地仓库,以供其他项目使用 部署 deploy 部署 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程 maven 仓库 本地(local) 中央(central) 私服 (private) 查找顺序,先在本地仓库查找依赖,如果没有的话去私服上找依赖,并缓存到本地,如果私服找不到,则去中央仓库中寻找,并缓存到私服。 maven 依赖引入在pom文件中使用dependency标签导入maven依赖 多重依赖解决方式路径最近者优先第一声明者优先 dependencyManagement: 能让子POM继承父POM的配置的同时, 又能够保证子模块的灵活性: 在父POMdependencyManagement元素配置的依赖声明不会实际引入子模块中, 但能够约束子模块dependencies下的依赖的使用(子模块只需配置groupId与artifactId, 见下).pluginManagement: 与dependencyManagement类似, 配置的插件不会造成实际插件的调用行为, 只有当子POM中配置了相关plugin元素, 才会影响实际的插件行为. maven 模块聚合和模块继承模块聚合推荐将聚合POM放在项目目录的最顶层, 其他模块作为聚合模块的子目录 模块继承子项目可以继承父项目的pom 推荐: 模块继承与模块聚合同时进行,这意味着, 你可以为你的所有模块指定一个父工程, 同时父工程中可以指定其余的Maven模块作为它的聚合模块. 但需要遵循以下三条规则: 在所有子POM中指定它们的父POM;将父POM的packaging值设为pom;在父POM中指定子模块/子POM的目录 超级pom-约定优先于配置任何一个Maven项目都隐式地继承自超级POM, 因此超级POM的大量配置都会被所有的Maven项目继承, 这些配置也成为了Maven所提倡的约定.","categories":[],"tags":[{"name":"Java 开发工具","slug":"Java-开发工具","permalink":"http://renjiahui.cn/tags/Java-%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"}]},{"title":"git","slug":"工具/git","date":"2019-06-28T16:00:00.000Z","updated":"2021-01-11T19:19:40.595Z","comments":true,"path":"2019/06/29/工具/git/","link":"","permalink":"http://renjiahui.cn/2019/06/29/%E5%B7%A5%E5%85%B7/git/","excerpt":"","text":"git 相关git fetch 拉取版本库变化 远程库命名 origin head 指针 指向当前操作的节点 checkout 切换分支 branch 创建新分支/不加名字可以查看当前本地分支,查看远程分支采用branch -a,删除分支用branch -d merge 分支合并 将其他合并到当前分支上,合并冲突问题 要没有冲突才可以merge,fast-forward模式,会丢失合并前的分支信息,使用–no-ff 禁用fast-forward,并产生一个新的提交commit。可以使用git log 查看提交历史。 git stash 临时保存 list 查看保存的现场 apply:恢复并保留记录,drop删除记录,pop 恢复并删除。 git status 查看工作区 git rebase 把代码建立在别人的基础上","categories":[],"tags":[{"name":"开发工具","slug":"开发工具","permalink":"http://renjiahui.cn/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"}]},{"title":"Java8新特性","slug":"java/java8New","date":"2019-06-22T16:00:00.000Z","updated":"2021-02-18T09:03:44.380Z","comments":true,"path":"2019/06/23/java/java8New/","link":"","permalink":"http://renjiahui.cn/2019/06/23/java/java8New/","excerpt":"","text":"1.接口默认方法可使用default关键字向接口添加非抽象方法的实现。 123456789interface Formula{ double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); }} 2.lambda表达式lambda运算符:所有的lambda表达式都是用新的lambda运算符 “ => “,可以叫他,“转到”或者 “成为”。运算符将表达式分为两部分,左边指定输入参数,右边是lambda的主体。lambda实质是对接口的实现。 lambda表达式: 1.一个参数:param -> expr 2.多个参数:(param-list)-> expr 1234567891011121314// 1. 不需要参数,返回值为 5 () -> 5 // 2. 接收一个参数(数字类型),返回其2倍的值 x -> 2 * x // 3. 接受2个参数(数字),并返回他们的差值 (x, y) -> x – y // 4. 接收2个int型整数,返回他们的和 (int x, int y) -> x + y // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void) (String s) -> System.out.print(s) lambda表达式作用域: 局部变量,必须用final修饰,否则在使用后无法被修改,(隐形设为了final)。 实例变量和静态变量,lambda均可以访问。 默认接口方法:不适用于lambda表达式。 lambda 使用场景: 3.函数式接口 “函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。使用@FunctionalInterface 注解进行声明,编译器会在编译的时候进行检查。 四大内置函数式接口 java.util.function包下: 函数式接口 参数类型 返回类型 用途 Consumer(消费型接口) T void 对类型为T的对象应用操作。void accept(T t) Supplier(供给型接口) 无 T 返回类型为T的对象。 T get(); Function<T, R>(函数型接口) T R 对类型为T的对象应用操作并返回R类型的对象。R apply(T t); Predicate(断言型接口) T boolean 确定类型为T的对象是否满足约束。boolean test(T t); PredicatesPredicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非) FunctionFunction 接口接受一个参数并生成结果。 SupplierSupplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。 ConsumersConsumer 接口表示要对单个输入参数执行的操作。 4.方法与构造函数引用Java 8允许您通过::关键字传递方法或构造函数的引用。 上面的示例显示了如何引用静态方法。 但我们也可以引用对象方法: 12345678910111213class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); }}class Test{ public static void main(String [] args) { Something something = new Something(); Converter<String, String> converter = something::startsWith; String converted = converter.convert(\"Java\"); System.out.println(converted); // \"J\" }} 构造器的方法引用 接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类: 1234567891011class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }} 接下来我们指定一个用来创建Person对象的对象工厂接口: 123interface PersonFactory<P extends Person> { P create(String firstName, String lastName);} 这里我们使用构造函数引用来将他们关联起来,而不是手动实现一个完整的工厂: 123//代码三PersonFactory<Person> personFactory = Person::new;Person person = personFactory.create(\"Peter\", \"Parker\"); 理解(个人向)定义了一个返回P对象的接口,在代码三中采用Person的构造函数对赋值给了这个接口的对象,在下面create调用中,自动调用了构造方法。 5.OptionalsOptionals不是函数式接口,而是用于防止 NullPointerException 的漂亮工具。这是下一节的一个重要概念,让我们快速了解一下Optionals的工作原理。 Optional 是一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是有时却什么也没有返回,而在Java 8中,你应该返回 Optional 而不是 null。 12345678910//of():为非null的值创建一个OptionalOptional<String> optional = Optional.of(\"bam\");// isPresent(): 如果值存在返回true,否则返回falseoptional.isPresent(); // true//get():如果Optional有值则将其返回,否则抛出NoSuchElementExceptionoptional.get(); // \"bam\"//orElse():如果有值则将其返回,否则返回指定的其它值optional.orElse(\"fallback\"); // \"bam\"//ifPresent():如果Optional实例有值则为其调用consumer,否则不做处理optional.ifPresent((s) -> System.out.println(s.charAt(0))); // \"b\" 6.Steamjava.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如java.util.Collection 的子类,List 或者 Set, Map 不支持。Stream 的操作可以串行执行或者并行执行。 首先看看Stream是怎么用,首先创建实例代码的用到的数据List: 123456789List<String> stringList = new ArrayList<>();stringList.add(\"ddd2\");stringList.add(\"aaa2\");stringList.add(\"bbb1\");stringList.add(\"aaa1\");stringList.add(\"bbb3\");stringList.add(\"ccc\");stringList.add(\"bbb2\");stringList.add(\"ddd1\"); Filterfilter 通过一个predicate接口来过滤元素,中间操作。 forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作 1234// 测试 Filter(过滤)stringList.stream() .filter((s) -> s.startsWith(\"a\")) .forEach(System.out::println); Sorted 排序是一个 中间操作,返回的是排序好后的 Stream。如果你不指定一个自定义的 Comparator 则会使用默认排序。 123456// 测试 Sort (排序)stringList .stream() .sorted() .filter((s) -> s.startsWith(\"a\")) .forEach(System.out::println);// aaa1 aaa2 Map面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。 123456// 测试 Map 操作stringList .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); MatchStream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是 最终操作 ,并返回一个 boolean 类型的值。 1234567891011121314151617181920// 测试 Match (匹配)操作boolean anyStartsWithA = stringList .stream() .anyMatch((s) -> s.startsWith(\"a\"));System.out.println(anyStartsWithA); // trueboolean allStartsWithA = stringList .stream() .allMatch((s) -> s.startsWith(\"a\"));System.out.println(allStartsWithA); // falseboolean noneStartsWithZ = stringList .stream() .noneMatch((s) -> s.startsWith(\"z\"));System.out.println(noneStartsWithZ); // true Count计数是一个 最终操作,返回Stream中元素的个数,返回值类型是 long。 1234567//测试 Count (计数)操作long startsWithB = stringList .stream() .filter((s) -> s.startsWith(\"b\")) .count();System.out.println(startsWithB); // 3 Reduce这是一个 最终操作 ,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规约后的结果是通过Optional 接口表示的: 1234567//测试 Reduce (规约)操作Optional<String> reduced = stringList .stream() .sorted() .reduce((s1, s2) -> s1 + \"#\" + s2);reduced.ifPresent(System.out::println); Parallel Streams(并行流)前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。 12345678910//并行排序long t0 = System.nanoTime();long count = values.parallelStream().sorted().count();System.out.println(count);long t1 = System.nanoTime();long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);System.out.println(String.format(\"parallel sort took: %d ms\", millis)); Maps前面提到过,Map 类型不支持 streams,不过Map提供了一些新的有用的方法来处理一些日常任务。Map接口本身没有可用的 stream()方法,但是你可以在键,值上创建专门的流或者通过 map.keySet().stream(),map.values().stream()和map.entrySet().stream()。","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"Java容器","slug":"java/Java容器","date":"2019-06-19T16:00:00.000Z","updated":"2021-02-18T09:02:07.503Z","comments":true,"path":"2019/06/20/java/Java容器/","link":"","permalink":"http://renjiahui.cn/2019/06/20/java/Java%E5%AE%B9%E5%99%A8/","excerpt":"","text":"容器类型主要有两种Collection 和 Map,Collection主要是集合,而Map是键值对映射。 为什么要有hashCode我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode: 当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。 hashCode()与equals()的相关规定 如果两个对象相等,则hashcode一定也是相同的 两个对象相等,对两个对象分别调用equals方法都返回true 两个对象有相同的hashcode值,它们也不一定是相等的 因此,equals方法被覆盖过,则hashCode方法也必须被覆盖 hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) 为什么两个对象有相同的hashcode值,它们也不一定是相等的?在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。 因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。 我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。 Iterator接口所有实现了Collection接口的容器类都有一个iterator()方法用以返回一个实现了Iterator接口的对象,这个对象可以是多种类型,不同的Collection实现类型遍历方式不同(用于遍历集合类)。 Iterator对象称作迭代器,用以方便的实现对容器内元素的遍历操作。 一句话总结,Iterator就是一个统一的遍历Collection中的元素的接口。 Iterator接口定义了如下方法 123456//判断游标右边是否有元素boolean hasNext();//返回游标右边的元素并将游标移动到下一个位置E next();//删除游标左边的元素,在执行完next之后该操作只能执行一次default void remove() Iterator对象的remove方法是在迭代过程中删除元素的唯一安全的方法。 增强的for循环对于遍历array或Collection的时候相当简便 缺陷:1.数组不能方便的访问下标值 2.集合与使用Iterator相比,不能方便的删除结合中的内容,在内部也是调用Iterator Map类集合 Map集合类 key value Super JDK 说明 HashTable 不允许为null 不允许为null Dictionary 1.0 线程安全(过时) ConcurrenHashMap 不允许为null 不允许为null AbstractMap 1.5 锁分段技术或CAS(JDK8及以上) TreeMap 不允许为null 允许为null AbstractMap 1.2 线程不安全(有序) HashMap 允许为null 允许为null AbstractMap 1.2 线程不安全(resize死链问题) HashMap均可以为null,而tree由于需要比较key,所以key不能为null,其他的都不允许为空。 TreeMap 底层采用红黑树。 TreeSet底层实现是采用TreeMap,而HashSet底层是采用HashMap实现 红黑树一种平衡二叉查找树,查找树(左节点上的值小于根节点,右节点上的值大于根节点),平衡查找树(左右子树的高度差的绝对值最大为1) 1)节点要么为红色,要么为黑色。(不然为啥叫红黑树;)) 2)根节点为黑色。 3)叶子节点为黑色。 (这两个简直送分,最上面和最下面都是黑的) 4)每个红色节点的左右孩子都是黑色。 (保证了从根节点到叶子节点不会出现连续两个红色节点) 5)从任意节点到其每个叶子节点的所有路径,都包含相同数目的黑色节点。(4,5是使得红黑树为平衡树的关键) 并发容器HashTable在所有put操作的时候都用synchronized进行加锁。 ConcurrentHashMap是由 Segment 数组、HashEntry 组成,和 HashMap 一样,仍然是数组加链表 采用volatile修饰value和链表的Entry保证多线程的可见性。 1.7版本: 采用segment进行分段,每段只能同时有一个线程操作,put时,先通过key定位到对应的segment,然后竞争时自旋获取对应段的锁。由于value属性是采用volatile修饰,因此get时无需加锁。 扩容的优化:原来是采用头插法进行链表的复制,高并发的情况下,可能会出现环链表。 1.8版本: 其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。 扩容机制修改了原来的头插法,改为了将原来的node分为保留的和新加的桶里,原来位置的元素只可能在i位置和i+oldcCap位置(hash的特性) 常用工具类Arrays 工具类 常用方法 binarySearch(byte[] a, byte key) 使用二分搜索法来搜索指定的 byte 型数组,以获得指定的值。 binarySearch(byte[] a, int fromIndex, int toIndex, byte key) 使用二分搜索法来搜索指定的 byte 型数组的范围,以获得指定的值。 copyOf(byte[] original, int newLength) 复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。 copyOfRange(boolean[] original, int from, int to) 将指定数组的指定范围复制到一个新数组。 equals(byte[] a, byte[] a2) 如果两个指定的 byte 型数组彼此相等,则返回 true。 fill(byte[] a, byte val) 将指定的 byte 值分配给指定 byte 节型数组的每个元素。 sort(byte[] a) 对指定的 byte 型数组按数字升序进行排序。 sort(byte[] a, int fromIndex, int toIndex) 对指定 byte 型数组的指定范围按数字升序进行排序。 toString(byte[] a) 返回指定数组内容的字符串表示形式。 sort算法timesort,归并加二分。 Collections 工具类 常用方法 addAll(Collection<? super T> c, T… elements) 将所有指定元素添加到指定 collection 中。 binarySearch(List<? extends Comparable<? super T>> list, T key) 使用二分搜索法搜索指定列表,以获得指定对象(实现Comparable接口)。 binarySearch(List<? extends T> list, T key, Comparator<? super T> c) 使用二分搜索法搜索指定列表,以获得指定对象(传入Comparator比较器)。 copy(List<? super T> dest, List<? extends T> src) 将所有元素从一个列表复制到另一个列表。 fill(List<? super T> list, T obj) 使用指定元素替换指定列表中的所有元素。 max(Collection<? extends T> coll) 根据元素的自然顺序,返回给定 collection 的最大元素。 max(Collection<? extends T> coll, Comparator<? super T> comp) 根据指定比较器产生的顺序,返回给定 collection 的最大元素。 min(Collection<? extends T> coll) 根据元素的自然顺序 返回给定 collection 的最小元素。 min(Collection<? extends T> coll, Comparator<? super T> comp) 根据元素的自然顺序 返回给定 collection 的最小元素。 reverse(List<?> list) 反转指定列表中元素的顺序。 sort(List list) 根据元素的自然顺序 对指定列表按升序进行排序。 sort(List list, Comparator<? super T> c) 根据元素的自然顺序 对指定列表按升序进行排序。 swap(List<?> list, int i, int j) 在指定列表的指定位置处交换元素。","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"mybatis","slug":"框架/mybatis","date":"2019-06-17T16:00:00.000Z","updated":"2021-01-11T19:20:38.215Z","comments":true,"path":"2019/06/18/框架/mybatis/","link":"","permalink":"http://renjiahui.cn/2019/06/18/%E6%A1%86%E6%9E%B6/mybatis/","excerpt":"","text":"JDBC工作流程(1) 加载JDBC驱动 (2) 建立并获取数据库连接 (3) 创建 JDBC Statements 对象 (4) 设置SQL语句的传入参数 (5) 执行SQL语句并获得查询结果 (6) 对查询结果进行转换处理并将处理结果返回 (7) 释放相关资源(关闭Connection,关闭Statement,关闭ResultSet) mybatis对JDBC的优化: (1) 使用数据库连接池对连接进行管理 (2) SQL语句统一存放到配置文件 (3) SQL语句变量和传入参数的映射以及动态SQL (4) 动态SQL语句的处理 (5) 对数据库操作结果的映射和结果缓存 (6) SQL语句的重复(SQL模块化) Mybatis基本结构和流程 流程: 当框架启动时,通过configuration解析config.xml配置文件和mapper.xml映射文件,映射文件可以使用xml方式或者注解方式,然后由configuration获得sqlsessionfactory对象,再由sqlsessionfactory获得sqlsession数据库访问会话对象,通过会话对象获得对应DAO层的mapper对象,通过调用mapper对象相应方法,框架就会自动执行SQL语句从而获得结果。 配置文件文档结构 configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器) 1 Properties这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置 123456<dataSource type=\"POOLED\"> <property name=\"driver\" value=\"${driver}\"/> <property name=\"url\" value=\"${url}\"/> <property name=\"username\" value=\"${username}\"/> <property name=\"password\" value=\"${password}\"/></dataSource> 2 Setting这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 1234567891011121314151617<settings> <setting name=\"cacheEnabled\" value=\"true\"/> <setting name=\"lazyLoadingEnabled\" value=\"true\"/> <setting name=\"multipleResultSetsEnabled\" value=\"true\"/> <setting name=\"useColumnLabel\" value=\"true\"/> <setting name=\"useGeneratedKeys\" value=\"false\"/> <setting name=\"autoMappingBehavior\" value=\"PARTIAL\"/> <setting name=\"autoMappingUnknownColumnBehavior\" value=\"WARNING\"/> <setting name=\"defaultExecutorType\" value=\"SIMPLE\"/> <setting name=\"defaultStatementTimeout\" value=\"25\"/> <setting name=\"defaultFetchSize\" value=\"100\"/> <setting name=\"safeRowBoundsEnabled\" value=\"false\"/> <setting name=\"mapUnderscoreToCamelCase\" value=\"false\"/> <setting name=\"localCacheScope\" value=\"SESSION\"/> <setting name=\"jdbcTypeForNull\" value=\"OTHER\"/> <setting name=\"lazyLoadTriggerMethods\" value=\"equals,clone,hashCode,toString\"/></settings> 3 类型别名(typeAliases)类型别名是为 Java 类型设置一个短的名字。 它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。 4 类型处理器(typeHandlers)无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。 要注意 MyBatis 不会通过窥探数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明那是 VARCHAR 类型的字段, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型 5 对象工厂(objectFactory)MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 6 插件(plugins)MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ParameterHandler (getParameterObject, setParameters) ResultSetHandler (handleResultSets, handleOutputParameters) StatementHandler (prepare, parameterize, batch, update, query) 通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。 7 环境配置(environments)MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例。 12345678910111213<environments default=\"development\"> <environment id=\"development\"> <transactionManager type=\"JDBC\"> <property name=\"...\" value=\"...\"/> </transactionManager> <dataSource type=\"POOLED\"> <property name=\"driver\" value=\"${driver}\"/> <property name=\"url\" value=\"${url}\"/> <property name=\"username\" value=\"${username}\"/> <property name=\"password\" value=\"${password}\"/> </dataSource> </environment></environments> 事务管理器(transactionManager)JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。 MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。 数据源(dataSource) 123456<dataSource type=\"org.myproject.C3P0DataSourceFactory\"> <property name=\"driver\" value=\"org.postgresql.Driver\"/> <property name=\"url\" value=\"jdbc:postgresql:mydb\"/> <property name=\"username\" value=\"postgres\"/> <property name=\"password\" value=\"root\"/></dataSource> 8 数据库厂商标识(databaseIdProvider)MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 9 映射器(mappers)既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要定义 SQL 映射语句了。 但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。 MyBatis-SpringMyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。 要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。 在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中: 123<bean id=\"sqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"> <property name=\"dataSource\" ref=\"dataSource\" /></bean> 注意:SqlSessionFactory 需要一个 DataSource(数据源)。 这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。 然后就可以使用sqlsession进行事务管理 mybatis学习依赖 123456789101112131415<!-- Mybatis springboot --><dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId></dependency><!-- druid阿里巴巴数据库连接池 --><dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId></dependency><!-- MySql数据库驱动 --><dependency> <groupId> mysql</groupId> <artifactId> mysql-connector-java</artifactId></dependency> mybatis组件功能配置(如开启各种功能,mapper文件位置等),使用xml,configure或者 properties 定义实体类对应数据库中的表结构 创建Dao接口,加上注解@mapper 创建对应接口的mapper.xml文件(用xml对接口进行实现,采用sql语句) 一些思考(个人向)关于sqlsession和jdbc的连接的关系,sqlsession是mybatis框架的一个对象,负责处理上层交付下来的sql,在若需要操作数据库,则会建立jdbc连接,操作数据库,若使用的是连接池,则会根据连接池的规则去使用和创建连接。 关于缓存的问题,mybatis的缓存由框架控制,下一部分会介绍,关于数据库部分自身的缓存则有数据库自身进行管理。 mybatis 缓存缓存架构 分为一级缓存和二级缓存 一级缓存为sqlsession的缓存,缓存只针对查询语句,一级缓存生命周期只存在于一个sqlsession对象。事务提交,插入更新和删除操作会自动更新缓存。也可以手动刷新缓存,关闭sqlsession会释放缓存。 二级缓存,默认不开启,需要手动设置开启,需要返回的pojo是可以序列化的,即实现了serializable接口的,二级缓存不同namespace是分开的,相同的namespace的对象是共享缓存的。二级缓存只有关闭sqlsession才会写入(实际存储在硬盘)性能问题?。","categories":[],"tags":[{"name":"Java 开发工具","slug":"Java-开发工具","permalink":"http://renjiahui.cn/tags/Java-%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"}]},{"title":"java异常","slug":"java/java异常","date":"2019-06-16T16:00:00.000Z","updated":"2021-01-11T19:41:53.689Z","comments":true,"path":"2019/06/17/java/java异常/","link":"","permalink":"http://renjiahui.cn/2019/06/17/java/java%E5%BC%82%E5%B8%B8/","excerpt":"","text":"原文出处: 代码钢琴家 简介 异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。java通过API中的Throwable类的众多子类来描述各种异常,java异常都是对象,是Throwable的子类。 java异常分类和类结构图从基类Throwable派生出两个类Error和Exception Error:错误,代表JVM本身的错误,错误不能被程序员通过代码处理,Error一般很少出现。Exception:异常,代表程序运行时发生的各种不期望发生的时间,可以被java异常处理机制使用,是我们说的异常处理的核心 对异常的处理要求我们将异常分为两类: 非检查异常(unckecked exception):编译器不要求强制处置的异常包括运行时异常(RuntimeException与其子类)和错误(Error)。 检查异常(checked exception):编译器要求必须处置的异常,除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。 异常处理的基本语法在编写代码处理异常时,对于检查异常,有2种不同的处理方式:使用try…catch…finally语句块处理它。或者,在函数签名中使用throws 声明交给函数调用者caller去解决。 try…catch…finally语句块 “try…catch…finally语句块”try…catch…finally语句块 123456789101112131415161718192021try{ //try块中放可能发生异常的代码。 //如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。 //如果发生异常,则尝试去匹配catch块。 }catch(SQLException SQLexception){ //每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。 //catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。 //在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。 //如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。 //如果try中没有发生异常,则所有的catch块将被忽略。 }catch(Exception exception){ //... }finally{ //finally块通常是可选的。 //无论异常是否发生,异常是否匹配被处理,finally都会执行。 //一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。 //finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。 } tips: try块和catch块中的局部变量和finally中的局部变量不能共享使用 一个catch块处理一个异常 当发生异常时,java将从异常发生的地方跳转到处理异常的代码,在处理完异常后,回到原来的控制流中去(类似中断?) throws 函数声明 throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。 throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。 finally块finally块不管异常是否发生,只要对应的try执行了,则它一定也执行。只有一种方法让finally块不执行:System.exit()。因此finally块通常用来做资源释放操作:关闭文件,关闭数据库连接等等。 良好的编程习惯是:在try块中打开资源,在finally块中清理释放这些资源。 需要注意的地方: 1、finally块没有处理异常的能力。处理异常的只能是catch块。 2、在同一try…catch…finally块中 ,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块。 3、在同一try…catch…finally块中 ,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,然后去外围调用者中寻找合适的catch块。 throw异常抛出语句 “throw异常抛出语句”)throw异常抛出语句 throw exceptionObject 程序员可以自己手动显式的抛出一个异常,throw语句的后面跟的是一个异常对象,和JRE自动抛出的异常没有区别 123456public void save(User user) { if(user == null) throw new IllegalArgumentException(\"User对象为空\"); //...... }","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"VSCODE配置","slug":"其他/VSCODE配置","date":"2019-05-10T16:00:00.000Z","updated":"2021-01-11T17:56:33.074Z","comments":true,"path":"2019/05/11/其他/VSCODE配置/","link":"","permalink":"http://renjiahui.cn/2019/05/11/%E5%85%B6%E4%BB%96/VSCODE%E9%85%8D%E7%BD%AE/","excerpt":"","text":"1安装官方插件 2-VSCODE-相关概念vs code 单次运行一个脚本视为一个task,配置文件为tasks.json。整个文件和多个文件夹视为workspace,配置文件为setting.json,调试环境配置为launch.json。这些文件需要手动编辑,编辑好后会替代默认配置生效。 3-setting-文件配置1.打开一个 .py文件,然后点状态栏右下角 python 2.再点图中的python 语言基础设置 3.修改工作空间settings.json 配置文件中的”python.pythonPath”为自己的解释器路径,工作空间配置可以代替用户配置,用户配置可以代替默认配置 4.配置 pylint 路径,因为默认 pylint 检查是开启的 5-配置运行参数-配置tasks-json-运行-python-脚本1.在打开的 py 文件中按 command+shift+B (windows 系统上 command 是 ctrl键)运行,点击配置生成任务 2.使用默认模版生成任务配置文件 tasks.json 3.点选 others 4.修改 tasks.py 1234567891011121314151617181920{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format \"version\": \"2.0.0\", \"tasks\": [ { \"label\": \"python\", \"type\": \"shell\", \"command\": \"C:/Users/yanta/AppData/Local/Programs/Python/Python36/python\", \"args\": [ \"${file}\" ], \"group\": { \"kind\": \"build\", \"isDefault\": true } } ] } 6-调试-单步运行-配置-launch-json前面都配置正常的话,此处不需要配置也可以正常运行,若不能正常运行可以尝试重启vscode再试,如果需要自定义调试配置,比如远程调试之类的话,可以添加 launch.json 并且修改其中相应的配置参数:1.点左侧的甲虫图标,然后点左上角没有配置,再点添加配置即可 12345678910111213141516171819\"version\": \"0.2.0\", \"configurations\": [ { \"name\": \"Python\", \"type\": \"python\", \"request\": \"launch\", \"stopOnEntry\": false, //\"pythonPath\": \"C:/Python27/python\", \"pythonPath\": \"C:/Users/yanta/AppData/Local/Programs/Python/Python36/python\", \"program\": \"${file}\", \"cwd\": \"${workspaceRoot}\", \"env\": {}, \"envFile\": \"${workspaceRoot}/.env\", \"debugOptions\": [ \"WaitOnAbnormalExit\", \"WaitOnNormalExit\", \"RedirectOutput\" ] }, 2.在行号的左侧单击即可设置断点,点左上角调试两个字右侧的绿色按钮,即可开始调试,上方会出现调试面板,有单步,继续等功能","categories":[],"tags":[{"name":"环境搭建","slug":"环境搭建","permalink":"http://renjiahui.cn/tags/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"}]},{"title":"Ubuntu18-04安装MySQL","slug":"其他/Ubuntu18-04安装MySQL","date":"2019-04-28T16:00:00.000Z","updated":"2021-01-11T19:39:46.404Z","comments":true,"path":"2019/04/29/其他/Ubuntu18-04安装MySQL/","link":"","permalink":"http://renjiahui.cn/2019/04/29/%E5%85%B6%E4%BB%96/Ubuntu18-04%E5%AE%89%E8%A3%85MySQL/","excerpt":"","text":"环境信息OS: Ubuntu18.04 MySQL:5.7.22 1-安装MySQL在Ubuntu中,可以直接采用apt-get的形式安装最新版本的MySQL,在安装前,建议更新一下apt软件列表。 1234#更新软件源 sudo apt-get update #安装mysql-server sudo apt-get install mysql-server 2-配置MySQL2-1启动MySQL配置1sudo mysql_secure_installation 根据提示操作即可,部分图示 2-2mysql服务运行状态12345678910111213mosesren@hadoop1:/etc/mysql/mysql.conf.d$ systemctl status mysql ● mysql.service - MySQL Community Server Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2019-03-12 14:39:23 CST; 9s ago Process: 121302 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid (code=exited, status=0/SUCCESS) Process: 121293 ExecStartPre=/usr/share/mysql/mysql-systemd-start pre (code=exited, status=0/SUCCESS) Main PID: 121304 (mysqld) Tasks: 27 (limit: 1085) CGroup: /system.slice/mysql.service └─121304 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid 3月 12 14:39:22 hadoop1 systemd[1]: Starting MySQL Community Server... 3月 12 14:39:23 hadoop1 systemd[1]: Started MySQL Community Server. 2-3配置远程访问MySQL默认是只能本地访问的,为了能够在windows环境下,使用连接工具访问,我们需要配置远程访问,修改/etc/mysql/my.cnf配置文件 首先用根用户登录,这里的会要求填写密码,如果在2.1中设置了,直接用即可,若未设置,直接不填即可登录, 进入root后,也可以用命令给root设置密码和权限 1GRANT ALL PRIVILEGES ON *.* TO root@localhost IDENTIFIED BY \"123456\"; 进行远程访问配置 12#这里的user填写自己对应的用户名,password填写对应的密码 GRANT ALL PRIVILEGES ON weixx.* TO user@\"%\" IDENTIFIED BY \"password\"; 修改/etc/mysql/mysql.conf.d/mysqld.conf配置文件 将其中的bind-address 注释掉即可 123mosesren@hadoop1:/etc/mysql/mysql.conf.d$ vim /etc/mysql/mysql.conf.d/mysqld.cnf #bind-address = 127.0.0.1 采用navicat登录这时候我们可以采用连接工具进行远程访问我们的数据库,navicat是收费的,也可以采用其他免费的工具。 配置好host(即mysql安装的机器的IP地址)和用户名密码即可访问。","categories":[],"tags":[{"name":"环境搭建 MySQL","slug":"环境搭建-MySQL","permalink":"http://renjiahui.cn/tags/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA-MySQL/"}]},{"title":"Java 基础","slug":"java/Java基础","date":"2019-04-19T16:00:00.000Z","updated":"2021-02-22T12:12:05.420Z","comments":true,"path":"2019/04/20/java/Java基础/","link":"","permalink":"http://renjiahui.cn/2019/04/20/java/Java%E5%9F%BA%E7%A1%80/","excerpt":"","text":"J2EE-J2SE-J2ME区别J2EE(Java 2 Platform Enterprise Edition)企业版 是为开发企业环境下的应用程序提供的一套解决方案。该技术体系中包含的技术如 Servlet Jsp等,主要针对于Web应用程序开发。J2SE(Java 2 Platform Standard Edition)标准版 是为开发普通桌面和商务应用程序提供的解决方案。该技术体系是其他两者的基础,可以完成一些桌面应用程序的开发。J2ME(Java 2 Platform Micro Edition)小型版 是为开发电子消费产品和嵌入式设备提供的解决方案。该技术体系主要应用于小型电子消费类产品,如手机中的应用程序等。 JDK-JRE-JVM的区别JVM(Java Virtual Machine Java虚拟机)可以理解为是一个虚拟出来的计算机,具备着计算机的基本运算方式,它主要负责将java程序生成的字节码文件解释成具体系统平台上的机器指令。让具体平台如window运行这些Java程序。 JRE(Java Runtime Environment Java运行环境)包括Java虚拟机(JVM Java Virtual Machine)和Java程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。 JDK(Java Development Kit Java开发工具包)JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了JDK,就不用在单独安装JRE了。其中的开发工具包括编译工具(javac.exe) 打包工具(jar.exe)等 简单而言:使用JDK开发完成的java程序,交给JRE去运行。三者关系: JVM:将字节码文件转成具体系统平台的机器指令。 JRE:JVM+Java语言的核心类库。 JDK:JRE+Java的开发工具。 equal和“==”的区别==:在直接值比较是比较值的大小,在引用值比较时比较值的地址是否相同equal :比较值是否相等 windows和linux文件路径问题windows平台:用\\或\\linux平台:用/ main方法123public static void main(String[] args) { } public:表示此方法可以被外部所调用 static:表示此方法可以由类名称直接调用 void:主方法是程序的起点,所以不需要任何返回值 main:系统规定好默认调用的方法名称,执行的时候,默认找到main方法名称 String args [] :表示的是运行时的参数,参数传递的形式:java类名称 参数1 参数2 参数3.. 常见的关键字 java注释 单行注释(line comment)用//表示 多行注释(block comment)用/**/表示 文档注释用/*/表示,是java特有的注释 java数据类型 基本数据类型 byte/8 char/16 short/16 int/32 float/32 long/64 double/64 boolean/~ boolean 只有true 和 false 用 1 bit 来存储 包装类型基本类型都有对应的包装类,基本类型和其对应的包装类型之间的赋值使用自动装箱和拆箱完成,Java中的基本类型功能简单,不具备对象的特性,为了使基本类型具备对象的特性,所以出现了包装类,就可以像操作对象一样操作基本类型数据。 12Integer x = 2; // 装箱 int y = x; // 拆箱 基本数据类型 对应的包装类 byte Byte short Short int Integer long Long char Character float Float double ouble boolean Boolean 缓存池 new Integer(123) 与 Integer.valueOf(123) 的区别在于: new Integer(123) 每次都会新建一个对象; Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。例如:Integer a = 10;调用的就是Integer.valueof()方法(自动装箱调用这个方法)默认的缓冲池的大小是-128到+127(可以在JVM的启动参数中修改)valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。 12345public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } 所有整数类型的类都有类似的缓存机制,基本类型对应的缓冲池如下: boolean values true and false all byte values short values between -128 and 127 int values between -128 and 127 char in the range \\u0000 to \\u007F String1. 数字转字符串 使用string 类的valueof方法 将数字装箱为基本类型,调用tostring方法 1234567891011121314public class TestNumber { public static void main(String[] args) { int i = 5; //方法1 String str = String.valueOf(i); //方法2 Integer it = i; String str2 = it.toString(); } } 2. 数字转字符串调用Integer的静态方法parseInt 123456789101112public class TestNumber { public static void main(String[] args) { String str = \"999\"; int i= Integer.parseInt(str); System.out.println(i); } } String Intern方法仅考虑jdk1.8 如果字符串常量池不存在对应的常量,则将 修饰符访问权限访问权限控制: public > protected > default > private 所有->其他包的非子类无法访问->子类无法访问->仅限本类 类修饰符:public(访问控制符),将一个类声明为公共类,他可以被任何对象访问,一个程序的主类必须是公共类。 abstract,将一个类声明为抽象类,没有实现的方法,需要子类提供方法实现。 final,将一个类生命为最终(即非继承类),表示他不能被其他类继承。 friendly,默认的修饰符,只有在相同包中的对象才能使用这样的类。 成员变量修饰符:public(公共访问控制符),指定该变量为公共的,他可以被任何对象的方法访问。 private(私有访问控制符)指定该变量只允许自己的类的方法访问,其他任何类(包括子类)中的方法均不能访问。 protected(保护访问控制符)指定该变量可以别被自己的类和子类访问。在子类中可以覆盖此变量。 friendly ,在同一个包中的类可以访问,其他包中的类不能访问。 final,最终修饰符,指定此变量的值不能变。 static(静态修饰符)指定变量被所有对象共享,即所有实例都可以使用该变量。变量属于这个类。 transient(过度修饰符)指定该变量是系统保留,暂无特别作用的临时性变量。 volatile(易失修饰符)指定该变量可以同时被几个线程控制和修改。 方法修饰符:public(公共控制符) private(私有控制符)指定此方法只能有自己类等方法访问,其他的类不能访问(包括子类) protected(保护访问控制符)指定该方法可以被它的类和子类进行访问。 final,指定该方法不能被重载。 static,指定不需要实例化就可以激活的一个方法。 synchronize,同步修饰符,在多个线程中,该修饰符用于在运行前,对他所属的方法加锁,以防止其他线程的访问,运行结束后解锁。 native,本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。 访问权限控制访问权限控制: 指的是本类及本类内部的成员(成员变量、成员方法、内部类)对其他类的可见性,即这些内容是否允许其他类访问。 Class和Object类Class类Class类也是类的一种,内容是创建的类的类型信息,Class只能由JVM去创建,没有public构造函数。 获取Class对象的方法 12345//使用forName()方法Class obj= Class.forName(\"shapes\");//使用getClass()方法Class obj=s1.getClass();Class obj1=s1.getSuperclass(); Objetc类123456789101112131415161718192021222324251.clone方法保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。2.getClass方法final方法,获得运行时类型。3.toString方法该方法用得比较多,一般子类都有覆盖。4.finalize方法该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。5.equals方法该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。6.hashCode方法该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。7.wait方法wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。调用该方法后当前线程进入睡眠状态,直到以下事件发生。(1)其他线程调用了该对象的notify方法。(2)其他线程调用了该对象的notifyAll方法。(3)其他线程调用了interrupt中断该线程。(4)时间间隔到了。此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。8.notify方法该方法唤醒在该对象上等待的某个线程。9.notifyAll方法该方法唤醒在该对象上等待的所有线程。 编码字符集和编码规范: 字符集: Unicode,为每个字符分配一个唯一的id(码点) 编码规范: UTF-8,将码点转换成字节序列的规则 多态和多态绑定一个对象变量可以指示多种实际类型的现象称为多态,在运行时能够自动选择适当的方法称为动态绑定 理解方法调用假设调用x.f(args),x声明为类C的一个对象,则调用方法的详细过程为: 编译器查看对象的声明类型和方法名,列举所有名为f的方法和父类中所有名为f的方法 确定方法调用中的参数类型,如果存在与提供类型完全匹配的方法,则选择这个方法,这个过程称为重载解析 为什么要重写hashcode和equals方法 重写equals方法是为了比较两个不同对象的值是否相等 重写hashCode是为了让同一个Class对象的两个具有相同值的对象的Hash值相等。 同时重写hashCode()与equals()是为了满足HashSet、HashMap等此类集合的相同对象的不重复存储。 java类加载器加载顺序BootstrapClassLoader 启动类加载器,加载$JAVA_HOME中jre/lib/rt.jar ExtensionClassLoader 标准扩展类加载器 AppClassLoader 系统类加载器 加载classPath下面的指定的jar包和class","categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"}]},{"title":"docker学习","slug":"其他/docker学习","date":"2019-04-14T16:00:00.000Z","updated":"2021-01-11T16:40:09.945Z","comments":true,"path":"2019/04/15/其他/docker学习/","link":"","permalink":"http://renjiahui.cn/2019/04/15/%E5%85%B6%E4%BB%96/docker%E5%AD%A6%E4%B9%A0/","excerpt":"","text":"docker-CE-安装学习os requirement: ubuntu 18.04 1.卸载旧的版本 1$ sudo apt-get remove docker docker-engine docker.io containerd runc 2.安装要用到的仓库更新apt 包索引 1$ sudo apt-get update 安装包,同时允许apt通过https使用仓库 123456$ sudo apt-get install \\ apt-transport-https \\ ca-certificates \\ curl \\ gnupg2 \\ software-properties-common 添加Docker的官方GPG key: 1$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 验证你的key的指纹 123456$ sudo apt-key fingerprint 0EBFCD88 pub 4096R/0EBFCD88 2017-02-22 Key fingerprint = 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 uid Docker Release (CE deb) sub 4096R/F273FCD8 2017-02-22 设置stable存储库 1234$ sudo add-apt-repository \\ \"deb [arch=amd64] https://download.docker.com/linux/ubuntu \\ $(lsb_release -cs) \\ stable\" 安装docker CE 1$ sudo apt-get install docker-ce 如果要安装特定版本的docker可以采用下面的方式: 1234567$ apt-cache madison docker-ce docker-ce | 5:18.09.1~3-0~ubuntu-xenial | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages docker-ce | 5:18.09.0~3-0~ubuntu-xenial | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages docker-ce | 18.06.1~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages docker-ce | 18.06.0~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages ... 然后 1$ sudo apt-get install docker-ce= VERSION_STRING 选择上一个命令中第二列中的对应版本即可。到此 docker 安装完毕,可以开始docker的使用了。 遇到的坑:我在安装的过程中,由于没有设置好ubuntu的apt的源,导致前面安装必要软件是,老是找不到依赖这里简单记一下,ubuntu更新国内镜像源的步骤:首先 将原来的源文件备份: 1$ sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 然后修改 sources.list文件,删除原来的内容,我这里采用清华的ubuntu镜像源,在页面中选择对应的ubuntu版本,然后将内容粘贴到sources.list文件中即可。 1$ vim /etc/apt/sources.list 将下面内容粘贴 123456789# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释 deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse 我之前就是因为没有注意版本问题,才导致更新一直失败!!!","categories":[],"tags":[{"name":"环境搭建","slug":"环境搭建","permalink":"http://renjiahui.cn/tags/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"}]},{"title":"hello-hexo","slug":"工具/hexo","date":"2019-01-22T16:00:00.000Z","updated":"2021-01-11T19:40:47.822Z","comments":true,"path":"2019/01/23/工具/hexo/","link":"","permalink":"http://renjiahui.cn/2019/01/23/%E5%B7%A5%E5%85%B7/hexo/","excerpt":"","text":"本文是建立这个博客写下的第一篇博文,记录这个博客建立的过程,今后要多写博客,不管有没有人看,分享一下自己的一些技术上的学习过程,和生活的一些体会等。 接下来这篇文章主要讲述我搭建博客的过程。 安装环境准备 git node.js github账号和腾讯云域名 前期工作安装git选择对应的系统版本下载即可 安装node.js选择对应的系统版本下载即可 安装hexo12# 安装 hexo 命令行工具 $ npm install hexo-cli -g 创建项目12345#创建一个你要放你的blog文件的文件夹,我这里是blog #初始化hexo $ hexo init blog $ cd blog $ npm install 新建完成后,指定文件夹的目录如下: 12345678. ├── _config.yml ├── package.json ├── scaffolds ├── source | ├── _drafts | └── _posts └── themes 在_config.yml文件中修改对应的配置,我这里修改了titel 网站标题和author 作者两项,其他可以参考官方文档进行修改。 查看博客配置完成后,可以运行 12$hexo generate $hexo server 生成静态文件并启动服务器,默认可以在http://localhost:4000/ 查看。 hexo主题设置hexo支持主题的自定义,我采用的是next主题,是用的比较多的一个主题。 首先下载主题,我这里采用git clone 的方式进行下载 1$ git clone https://github.com/iissnan/hexo-theme-next themes/next 然后设置主题,修改根目录下的_config.yml文件 1theme: next 接下来对主题里的一些选项进行修改,首先修改菜单栏,添加你需要的页面,我这里添加关于页面about 123456789menu: home: / || home about: /about/ || user #tags: /tags/ || tags #categories: /categories/ || th archives: /archives/ || archive # schedule: /schedule/ || calendar # sitemap: /sitemap.xml || sitemap # commonweal: /404/ || heartbeat 其他的根据需要修改即可。配置访客统计,next集成了busuanzi的访问统计功能,只要在主题的配置文件中打开即可。 12345678910111213141516# Show PV/UV of the website/page with busuanzi. # Get more information on http://ibruce.info/2015/04/04/busuanzi/ # 增加不蒜子访问数量统计 busuanzi_count: enable: true # custom uv span for the whole site site_uv: true site_uv_header: 访问人数 site_uv_footer: # custom pv span for the whole site site_pv: true site_pv_header: 访问总量 site_pv_footer: 次 page_pv: true page_pv_header: 浏览 page_pv_footer: 次 将enable 设为true即可 备注:由于busuanzi(不蒜子)的网址更新,导致了使用Hexo Next主题时统计浏览数失效.解决方法:到hexo的themes文件夹下, 进入\\themes\\next\\layout_third-party\\analytics打开: busuanzi-counter.swig 将src=“https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js” 修改为src=“https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js” 即可。 接下来博客已经搭建好了我们需要将我们生成的静态文件部署到我们的github上去,就可以通过github访问我们的博客了。 github设置首先注册一个github账号,我本来就有,这里略去。然后新建一个仓库,要注意的是,我们建的项目名一定要是 用户名.github.io 的形式,这样才能生成我们的页面。仓库建立好之后,我们需要将内容同步到我们的github上去。 首先我们配置全局的git 账号和邮箱 12git config –-global user.name “xxxx” //(“”的账号是刚才Github里面自己注册的账号) git config –-global user.email “xxxx@qq.com” //(\"\"的邮箱是你自己注册的邮箱) 然后生成ssh密钥,并设置到github上 12cd ~/.ssh ssh-keygen -t rsa -C “xxxx@qq.com” //打自己的邮箱 接下来,复制生成的密钥 id_rsa.pub,到github的设置页面的 SSH and GPG keys 里面新建一个,并把密钥的内容粘贴上去。测试ssh是否成功 1ssh -T git@github.com 上传hexo安装部署到github上的插件依赖 1npm install –save hexo-deployer-git 然后修改根目录下的_config.yml文件夹deploy:type: gitrepo: git@github.com:xxxx/xxxxx.github.io.git //(这里改成自己的用户名和用户名加域名,可以在仓库的右上角直接复制)branch: master接下来运行 1hexo deploy 即可完成部署。 查看博客过几分钟后,在浏览器输入 用户名.github.io 即可看到自己的博客。 博客搭建成功","categories":[],"tags":[{"name":"建站历程","slug":"建站历程","permalink":"http://renjiahui.cn/tags/%E5%BB%BA%E7%AB%99%E5%8E%86%E7%A8%8B/"}]}],"categories":[],"tags":[{"name":"java","slug":"java","permalink":"http://renjiahui.cn/tags/java/"},{"name":"开发工具","slug":"开发工具","permalink":"http://renjiahui.cn/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"},{"name":"zookeeper 分布式","slug":"zookeeper-分布式","permalink":"http://renjiahui.cn/tags/zookeeper-%E5%88%86%E5%B8%83%E5%BC%8F/"},{"name":"学习笔记","slug":"学习笔记","permalink":"http://renjiahui.cn/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"大数据","slug":"大数据","permalink":"http://renjiahui.cn/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"},{"name":"分布式","slug":"分布式","permalink":"http://renjiahui.cn/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"},{"name":"Java","slug":"Java","permalink":"http://renjiahui.cn/tags/Java/"},{"name":"Java redis","slug":"Java-redis","permalink":"http://renjiahui.cn/tags/Java-redis/"},{"name":"中间件 MQ","slug":"中间件-MQ","permalink":"http://renjiahui.cn/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6-MQ/"},{"name":"框架学习 Java","slug":"框架学习-Java","permalink":"http://renjiahui.cn/tags/%E6%A1%86%E6%9E%B6%E5%AD%A6%E4%B9%A0-Java/"},{"name":"Java 开发工具","slug":"Java-开发工具","permalink":"http://renjiahui.cn/tags/Java-%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"},{"name":"Java 中间件","slug":"Java-中间件","permalink":"http://renjiahui.cn/tags/Java-%E4%B8%AD%E9%97%B4%E4%BB%B6/"},{"name":"计算机基础","slug":"计算机基础","permalink":"http://renjiahui.cn/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/"},{"name":"数据库","slug":"数据库","permalink":"http://renjiahui.cn/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"},{"name":"Java 面试","slug":"Java-面试","permalink":"http://renjiahui.cn/tags/Java-%E9%9D%A2%E8%AF%95/"},{"name":"环境搭建","slug":"环境搭建","permalink":"http://renjiahui.cn/tags/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"},{"name":"环境搭建 MySQL","slug":"环境搭建-MySQL","permalink":"http://renjiahui.cn/tags/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA-MySQL/"},{"name":"建站历程","slug":"建站历程","permalink":"http://renjiahui.cn/tags/%E5%BB%BA%E7%AB%99%E5%8E%86%E7%A8%8B/"}]}