-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 183 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 183 KB
1
{"meta":{"title":"FunriLySpace","subtitle":"天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。","description":"天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。","author":"FunriLy","url":"https://zggdczfr.cn"},"pages":[{"title":"","date":"2017-02-25T12:10:17.601Z","updated":"2017-02-25T12:10:17.601Z","comments":true,"path":"README.html","permalink":"https://zggdczfr.cn/README.html","excerpt":"","text":"MyBlogMy Personal Website个人博客 : https://zggdczfr.cn/ hexo主题 : http://github.com/ppoffice/hexo-theme-icarus"},{"title":"MY WORKS","date":"2017-02-02T08:33:28.000Z","updated":"2017-02-02T08:33:28.572Z","comments":true,"path":"my_works/index.html","permalink":"https://zggdczfr.cn/my_works/index.html","excerpt":"","text":""},{"title":"aboutme","date":"2017-02-04T09:49:51.000Z","updated":"2017-02-07T08:59:35.180Z","comments":true,"path":"aboutme/index.html","permalink":"https://zggdczfr.cn/aboutme/index.html","excerpt":"","text":"FunriLy目前就读于广东工业大学计算机学院某系。自己本来就是个半瓢水的逗比。本来语文都没学好,却还要装逼秀英语选择了计算机专业。一阵子折腾下来,只学会了多种编程语言的”Hello Word !”。同时作为一名专业的码农搬砖工,主写JAVA后端,却老是去搞各种奇奇怪怪的东西。生活中,喜欢组织带人去各种浪,浪到天昏地暗,可骨子里却暗藏着2.5次元的宅属性~~~(。•ˇ‸ˇ•。) 哼!都怪你们(`ȏ´)也不哄哄人家(〃′o`)人家超想哭的,捶你胸口,大坏蛋!!! ( ̄^ ̄)ゞ咩QAQ 捶你胸口 你好讨厌!(=゚ω゚)ノ要抱抱嘤嘤嘤哼,人家拿小锤锤捶你胸口!!!(。• ︿•̀。)大坏蛋,打死你(つд⊂)"},{"title":"TAGS","date":"2017-02-02T08:34:11.000Z","updated":"2017-02-02T08:34:11.760Z","comments":true,"path":"tags/index.html","permalink":"https://zggdczfr.cn/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"聊一聊SpringAop","slug":"聊一聊SpringAop","date":"2018-01-31T01:50:27.000Z","updated":"2018-01-31T05:51:10.704Z","comments":true,"path":"2018/01/31/聊一聊SpringAop/","link":"","permalink":"https://zggdczfr.cn/2018/01/31/聊一聊SpringAop/","excerpt":"最近重温了一下《Spring 源码深度解析》,从学习相关知识以来越发觉得 Aop(切面编程) 的好用。于是简单做一下笔记记录以及资料收集。","text":"最近重温了一下《Spring 源码深度解析》,从学习相关知识以来越发觉得 Aop(切面编程) 的好用。于是简单做一下笔记记录以及资料收集。 聊一聊 Spring AOP什么是 AOP ? AOP,面向切面编程,通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。AOP,是 Spring 框架中一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 —— 来源于网络 通常情况下,我们都是使用 框架自动创建AOP代理,它可以分为静态代理和动态代理。在Spring中,静态代理采用了AspectJ,而动态代理根据应用场景选用了 JDK动态代理 或者 CGLIB动态代理。(代理部分知识详见 Java中的代理) 使用 AspectJ 实现 AOP首先,要明白的一点 : AspectJ 采用了静态织入,就是在编译期就织入(编译出来的 class 文件,字节码就已经织入)。关于如何在 Spring 中采用静态 AOP 的资料网上有许多,也并没有什么特别之处,所以笔记就不再啰嗦这一部分。(具体 Google 或者 Baidu) 知识碎片 在传统的 Spring 项目(基于 XML 配置)中,我们是采用 <aop:aspectj-autoproxy/>来启用 AOP;而在 SpringBoot 项目中,则是采用注解@EnableAspectJAutoProxy 来开启AOP。 JDK 1.5 引入了 java.lang.instrument ,提供给使用者来实现一个 Java agent,并通过 agent 来修改类的字节码。(补充,什么是 Java agent ?就是运行在 main 方法之前的拦截器) 在 Spring 中静态 AOP 直接使用了 AspectJ 提供的方法,而 AspectJ 是基于 Instrument 基础上进行的封装。 举个栗子,假如我们对一个方法使用AspectJ的编译增强实现 AOP : Aspect 会自动将我们调用该方法的语句转为调用到我们指定的 AOP 实现类,并在实现类中回调我们真正想调用的方法(基本思路就是 : 替换 -> 调用AOP类 -> 回调目标对象)。 动态代理实现 AOPSpring 中动态 AOP 采用了 JDK动态代理 和 CGLIB动态代理。所谓的 动态AOP 其实就是指框架本身并不去修改字节码,而是临时为方法生成一个 AOP 对象(包含了目标对象所有的同名方法),该对象在特定的切点进行回调目标对象的真实方法。 Spring AOP 如何实现动态代理? 完成所有 Bean 的加载,并遍历所有 beanName 提取出声明 AspectJ 注解的类。 对提取出来是类进行增强器寻找。 首先是获取切点信息(就是类似于 @Before 之类的注解) 根据不同的注解生成不同的增强器。 创建代理对象。 将结果加入到缓存中以方便调用。 综上,Spring AOP 框架所做的任务就是根据目标对象构造各类增强器,并组成拦截器链,但真正利用代理模式调用到目标对象之时依照切点信息执行拦截器链上的对象(方法)。关于这部分建议仔细阅读《Spring 源码深度解析》。 Spring 动态代理策略 如果目标对象实现了接口,默认情况下会采用 JDK 动态代理实现 AOP。 如果目标对象实现了接口,可以强制使用 CGLIB 实现 AOP。 如果目标对象没有实现接口,必须采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 之间转换。 (补充 : JDK 动态代理只能针对接口的类生成代理,在代理类中调用业务实现类的同名方法; CGLIB 是针对类实现代理,对指定的类生成子类并覆盖其中的方法,是一种继承的关系) AOP 代理备注 效率上,静态代理相对高效于动态代理。 静态代理是在虚拟机启动时通过改变目标对象的字节码来完成对目标对象的增强;而动态代理在调用过程中,还需要动态创建代理类并代理目标对象。 由此,当采用静态代理时,系统给再次调用目标类和调用正常的类并无差别。 两种动态代理的区别: JDK 动态代理:代理对象必须是某个接口的实现,通过在运行期间创建一个接口的实现类来完成对目标对象的代理。 CGLIB 代理:原理类似前者,只是在运行期间生成了针对于目标对象扩展的子类,其底层通过了 ASM(来源 Java 字节码编辑类库) 操作字节码来实现,其性能比 JDK 动态代理强。","categories":[{"name":"Java 学习笔记","slug":"Java-学习笔记","permalink":"https://zggdczfr.cn/categories/Java-学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"},{"name":"Spring Aop","slug":"Spring-Aop","permalink":"https://zggdczfr.cn/tags/Spring-Aop/"}]},{"title":"方","slug":"方","date":"2018-01-26T13:14:33.000Z","updated":"2018-03-04T05:26:37.856Z","comments":true,"path":"2018/01/26/方/","link":"","permalink":"https://zggdczfr.cn/2018/01/26/方/","excerpt":"不知道为什么,这几天处于极度低迷状态……想出去散散心,发现几个死党回家的回家、陪女票的陪女票、常住实验室的住实验室……自己看书也看不下去,就开启了疯狂看电影模式(感谢胖纸借给我的会员),并随手写一点什么吧。反正也没心思写什么鬼技术笔记了,就想到什么写什么好了。","text":"不知道为什么,这几天处于极度低迷状态……想出去散散心,发现几个死党回家的回家、陪女票的陪女票、常住实验室的住实验室……自己看书也看不下去,就开启了疯狂看电影模式(感谢胖纸借给我的会员),并随手写一点什么吧。反正也没心思写什么鬼技术笔记了,就想到什么写什么好了。 方关于耐心从现在看来,如今的“冷静”下常常隐藏着一些胆怯与暴躁。感谢大学里的“小社会”,感觉在处理任何事情上,都需要有耐心……因为不耐心的话只会导致与其他人的矛盾升级。当你在尽心尽力完成任务时,你永远也猜不到你的“服务对象”现在在干嘛,也许等到了截止的那一天,他才跳出来指责你“为什么不早一点提醒他”……看得多了,也就见怪不怪了。 还好大学里大部分事情是可以后悔的。渐渐地,也没了当初的那股热情,只想踏踏实实地完成自己的本职工作。希望在接下来的真正步入社会的实践阶段,能够好好消磨一下自己的急躁,并能够找到以前的热情吧! 升学还是就业还有时间,还没做下真正的决定,虽然家人希望我能够继续升学,我本人还是倾向于就业。感谢我的家人,将决定权完全交给我自己。 智商与情商智商很重要,情商也很重要!在这个社会,很多时候智商不会给你带来太多的影响,过低的情商一定给你减分。 学习离不开谦虚与请教我自己一直认为我的智商并不高,甚至还觉得远不如印象中的某些人……但至少在前面许多前辈、大佬,我一直保持着谦虚的态度,这也是我自我感觉这几年过得比较充实的原因吧。一个人,不可能一辈子都呆在自己已经熟悉的领域内,对自己不懂的事务时刻保持着谦虚的态度。举个栗子,如果以后有幸加入了某间公司,就将会有新的生活工作方式,小到开发工具编码习惯、大到生活规律场所之类的。万事,保持着一颗谦虚的心。 不想被别人“一问三不知”,那就先问别人吧。谦虚地请教别人,并不是什么掉身份的事情,何况自己现在也没什么身份可掉!当然这种请教也有一个度,最终的把握还是要自己实践,这一点从工作室师兄前辈对我们的教导方式就可以感受到了。 开源乐于分享,也一直努力往开源上靠,可到现在还有一点遗憾 : 没有真正为开源项目贡献过一行代码……首先自己的技术还没达到心中的标准,同时也是对自己没信心吧。 人性这几天,疯狂观看韩国的改编电影(源于社会真实事件),有三点感触:1、不要让小孩子一个人独处!!!2、韩国的警察太垃圾。3、对于人性的思考。 不可否认,这些电影都极大反映了人性的阴暗面,但细看其中我觉得那些“正义”类的人性更能吸引我。人性是复杂的,但社会还是美好的……做好自己的良心即可。 2018/01/26 FunriLy 留 一个月的假期,给自己两年来放了一个最后的长假。原本以为过年上班后才能接到各位大佬的电话,不料在年前年后各接到了一次,对于人才各个公司也是非常重视的。过得有点焦急的年……等电话真的等到非常煎熬,想起第一个电话之后我要平静整整半个多小时才能坐下吃饭,哈哈哈。不过这种煎熬,真的不想经历太多次,找到比较满意了就可以结束了!!! 今年也开了个好头,第一次给自己最重要的家人包上了红包,里面的心意也是我大学期间靠学习比赛所获(没办法,姐一直不允许我去兼职,其实我也认为大学不应该将时间浪费在发传单之类的兼职)。 今年开始,我希望老师能够放过我……不要我再紧跟着项目了,我也该好好为暑假实习以及大四毕业做准备了。还有好多书没看、还有好多知识点记不牢……该gun去看书啦!!!希望我能尽快就业,老爸就能真正做小或者退掉家里经营的生意了,还是希望老爸老妈换一种轻松一点的职业。立个 flag ,目标2020年! 2018/03/04 FunriLy 留 未完待续……看看我哪一天能够想起来","categories":[{"name":"随记感悟","slug":"随记感悟","permalink":"https://zggdczfr.cn/categories/随记感悟/"}],"tags":[]},{"title":"重温JDK源码之ArrayList","slug":"重温JDK源码之ArrayList","date":"2018-01-24T05:29:51.000Z","updated":"2018-01-24T12:07:57.984Z","comments":true,"path":"2018/01/24/重温JDK源码之ArrayList/","link":"","permalink":"https://zggdczfr.cn/2018/01/24/重温JDK源码之ArrayList/","excerpt":"重温集合源码系列 之 ArrayList 篇(基于 JDK1.8)","text":"重温集合源码系列 之 ArrayList 篇(基于 JDK1.8) 重温 JDK 源码之 ArrayList一、ArrayList 定义 12public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{} ArrayList 是一个动态数组队列,其特点就是能够动态扩展数组容量。 继承了 AbstractList 类,实现了 List 接口,提供了相关的增删查改等操作,并且其操作对象为包括 null 在内的所有类。 实现了 RandomAccess 接口,提供了快速随机访问的功能(可以通过元素序号快速获得元素对象)。 实现了 Cloneable 接口,覆盖了 clone() 方法。 实现了 Serializable 接口,支持 Java 序列化传输。 对于 ArrayList 的操作不是线程安全。所以在多线程中使用 ArrayList 时,需要注意一下线程控制。如: List<Integer> list = Collections.synchronizedList(new ArrayList<>()); 二、ArrayList 重要属性 123456// 初始化默认数组大小private static final int DEFAULT_CAPACITY = 10;// 记录动态数组实际大小private int size;// Object[] 类数组,用于存储真实的元素对象transient Object[] elementData; // non-private to simplify nested class access 三、ArrayList 构造方法123456789101112131415161718192021222324public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException(\"Illegal Capacity: \"+ initialCapacity); }}public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; }} ArrayList 类提供了三个构造方法。 构造方法一 : 调用默认的共享数组实例,一个初始容量为10 的空数组。 构造方法二 : 根据initialCapacity 来初始化 elementData 数组的大小。 构造方法三 : 将提供的集合转换为数组并返回给 elementData。 调用 toArray() 方法将集合转换为数组。 若数组不为0,并且 toArray() 返回不是 Object[] 类,则调用 Arrays.copyOf() 方法将其转换为 Object[]。 四、ArrayList 重要方法解析 1、增加123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 // 添加一个元素public boolean add(E e) { ensureCapacityInternal(size + 1); // 数组容量检查 elementData[size++] = e; //将元素添加至List数据尾部,容量+1 return true; } // 在指定位置添加一个元素public void add(int index, E element) { rangeCheckForAdd(index); //检查添加范围,判断索引是否越界 ensureCapacityInternal(size + 1); // 数组容量检查 // 对数组进行复制处理,目的就是空出index的位置插入element,并将index后的元素位移一个位置 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; // 将指定的index位置赋值为element size++; //容量+1 }// 添加一个集合 public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); //转化为数组,并一定是 Object[] 数组 int numNew = a.length; ensureCapacityInternal(size + numNew); // 动态检查容量 // 将整个数组复制到List数据尾部 System.arraycopy(a, 0, elementData, size, numNew); size += numNew; // 容量+数组长度 return numNew != 0; }// 在指定位置添加一个集合 public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); //检查添加范围,判断索引是否越界 Object[] a = c.toArray(); //转化为数组 int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; // 计算需要移动的长度 // 数组复制,空出第index到index+numNum的位置,即将数组index后的元素向右移动numNum个位置 if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); // 将要插入的集合元素复制到数组空出的位置中 System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }//检查添加范围,判断索引是否越界 private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }// 数组容量检查,不够时进行扩容 private void ensureCapacityInternal(int minCapacity) { // 如果是空数组 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }// 检查是否需要扩容 private void ensureExplicitCapacity(int minCapacity) { modCount++; // 记录结构被修改的次数 // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }// 扩容操作 private void grow(int minCapacity) { int oldCapacity = elementData.length; //当前数组长度 // 加上0.5倍容量:>> 右移运算符,相当于除以2 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果新扩容的数组长度还是比最小需要的容量小,则以最小需要的容量为长度进行扩容 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新扩容的数组长度比数组最大容量还要大,重新检查扩容最小标准,并将结果返回给新扩容数组长度 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 进行数据拷贝,Arrays.copyOf底层实现是System.arrayCopy() elementData = Arrays.copyOf(elementData, newCapacity); } 2、删除123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 // 根据索引位置删除元素public E remove(int index) { rangeCheck(index); // 数组越界检查 modCount++; E oldValue = elementData(index); // 取出要删除的元素 int numMoved = size - index - 1; // 数组复制,将index之后的元素前移一个位置 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 将数组最后一个元素置空并数量-1 elementData[--size] = null; // clear to let GC do its work return oldValue; }// 根据元素内容删除,只删除匹配的第一个 public boolean remove(Object o) { // 对要删除的元素进行null判断 if (o == null) { // 因为 null 需要使用 == 来比较 for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { // 非 null 需要使用 equals 来比较 for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }// 私有移除方法,跳过边界检查 private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }// 边界检查 private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } 3、更新1234567 // 更新指定索引位置的元素public E set(int index, E element) { rangeCheck(index); // 数组越界检查 E oldValue = elementData(index); // elementData(index)就是elementData[index] elementData[index] = element; // 重置为新元素 return oldValue; } 4、寻找12345 // 根据索引获取元素public E get(int index) { rangeCheck(index); // 数组元素越界检查 return elementData(index); // 获取元素 } 5、是否包含123456789101112131415161718192021222324252627282930// 检查集合是否包含某个元素public boolean contains(Object o) { return indexOf(o) >= 0;}// 从头到尾遍历查询数组中元素public int indexOf(Object o) { if (o == null) { // 需要判断是否是 null 主要是使用 == 与 equals 的区别 for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { // 若目标元素非 null for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1;}// 从尾到头遍历查询数组元素public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1;} 6、释放空间123456789 // 将容量空间调整为当前实际元素数量大小,从而释放空间public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } 7、其他 ArrayList 是一个动态扩展数组,其默认容量为10。 ArrayList 扩容 : 新容量 = 旧容量 + 旧容量>>2,并且扩容伴随着开辟新空间、进行数组复制。 ArrayList 的 clone() 方法就是将所有的元素都克隆到一个新数组。 五、ArrayList 遍历方式 ArrayList 支持三种遍历方式。 1通过迭代器(Iterator)遍历12345Object value = null;Iterator it = list.Iterator();while(it.hasNext()){ value = (Object) it.next();} 2、通过索引值遍历12345Object value = null;int size = list.size();for(int i=0; i<size; i++){ value = (Object) list.get(i);} 3、Foreach 循环遍历1234object value = null;for(Object obj : list){ value = obj;} 4、三种遍历的效率比较 RandomAccess 遍历方法效率最高;在数量较小的情况下,Iterator 和 Foreach 的效率较为暧昧;随着数量增加,Iterator 的效率要高于 Foreach。 RandomAccess 遍历方法效率最高。 ArrayList 实现 RandomAccess 接口。查看 RandomAccess 接口注释可以看到一段有趣的解释 : 123456789* <pre>* for (int i=0, n=list.size(); i &lt; n; i++)* list.get(i);* </pre>* runs faster than this loop:* <pre>* for (Iterator i=list.iterator(); i.hasNext(); )* i.next();* </pre> Iterator 的效率要高于 Foreach。 Foreach 是调用 Iterator 来实现的,同时附带其他辅助性操作(如,赋值等),所以Iterator 的效率要高于 Foreach。 Foreach 语法糖经过编译器处理成了Iterator的遍历,有关foreach语法糖的细节可以参考《Java语法糖之foreach》。","categories":[{"name":"Java 学习笔记","slug":"Java-学习笔记","permalink":"https://zggdczfr.cn/categories/Java-学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"},{"name":"集合","slug":"集合","permalink":"https://zggdczfr.cn/tags/集合/"},{"name":"源码","slug":"源码","permalink":"https://zggdczfr.cn/tags/源码/"}]},{"title":"聊一聊ThreadLocal","slug":"聊一聊ThreadLocal","date":"2018-01-22T07:24:34.000Z","updated":"2018-01-22T13:58:02.046Z","comments":true,"path":"2018/01/22/聊一聊ThreadLocal/","link":"","permalink":"https://zggdczfr.cn/2018/01/22/聊一聊ThreadLocal/","excerpt":"JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。 ——《百度百科》","text":"JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。 ——《百度百科》 聊一聊 ThreadLocal一、什么是 ThreadLocal ? ThreadLocal,线程局部变量,就是为每一个使用该变量的线程提供一个副本,同时每个线程都能独立地改变属于自己的副本而不会与其他线程的副本产生冲突。 在多线程共享资源上,我们常常需要控制线程对于资源的访问顺序、数目等来达到同步的效果。而 ThreadLocal 刚好与此相反,JVM 为每一个线程都绑定了本地存取的空间,各个线程访问属于自己的空间互不干扰。 二、ThreadLocal 定义 123456789101112public class ThreadLocal<T> { private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); /*** 省略部分参数、方法 ***/ // 仅显示两个主重要的方法 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }} 从ThreadLocal 的概念和定义方法,我们可以知道其内部维护有且只有一个 Map 键值对,并且各个线程之间不能相互访问。好了,那么接下来就需要解决两个问题了,一个是线程如何维护自己独立的 Map ?另外,Map 的数据结构与普通的 Map 有什么区别(当然有区别了,不然也没必要定义内部类)? 如何维护 ThreadLocalMap?平时的 Map 操作很简单,你现在需要解决的是然后避免自己的 Map 被其他线程访问到。我们继续跳进去源码中 : 123456789101112131415161718192021public void set(T value) { Thread t = Thread.currentThread(); // 获取当前线程 ThreadLocalMap map = getMap(t); // getMap() 方法的源码在前面 if (map != null) map.set(this, value); else createMap(t, value);}public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); // 获取当前 ThreadLocal 的变量值 if (e != null) { @SuppressWarnings(\"unchecked\") T result = (T)e.value; return result; } } return setInitialValue(); // 若当前线程还未创建ThreadLocalMap,则返回调用此方法并在其中调用createMap方法进行创建并返回初始值。} 追踪进去 Thread 类(JVM 为每一个线程都绑定了本地存取的空间) : 123456publicclass Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;} 这段源码很容易阅读,大意就是说,Thread 本身只有有一个 ThreadLocalMap,但线程局部变量执行 set 操作时,根据线程绑定只能对于本线程的局部变量操作。 ThreadLocalMap 的数据结构继续跟踪进源码 : 1234567891011121314static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private static final int INITIAL_CAPACITY = 16; // 初始化容量 private Entry[] table; /*** 省略 ***/} 其大意就是说,在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象。接下来,我们往其中放入一个元素: 123456789101112131415161718192021222324252627private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); // 计算 hash 值 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { // 解决冲突 replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();} 从这段源码,我们知道当执行插入操作时,先计算 ThreadLocal 对象的 hash 值从而定位到 table 数组的位置 i 。若当前位置为空,则初始化 Entry 对象并返回。若当前位置的 key 值相同,则执行更新操作。若当前位置已经有元素存在(发生冲突),那只能找下一个空位。 三、ThreadLocal 的同步机制在正常的同步机制中,我们一般通过对象的锁机制来保证同一时间只有一个线程访问共享变量(用时间来换取空间),这样子程序设计以及编写难度较大。而 ThreadLocal 采用了独立变量副本来隔绝多线程对数据访问的冲突(用空间来换取时间)。 但是,ThreadLocal 不是为了解决多线程访问的问题,其只是为每个线程提供了一个独立的变量副本。 一般情况下,ThreadLocal 主要应用将线程对应到实例的场景中,并且要求这个实例会很频繁应用到。举个栗子,比如聊天网站登录用户的信息:当用户登录成功后,如果用到了 WebSocket 之类的长连接,服务器端会保持线程的长久性,此时在线程中创建 ThreadLocal 用于存储用户基本信息,这样子在页面跳转时不会受到影响,同时也避免了对共享变量的频繁读写。类似于这种场景采用 ThreadLocal 来说是比较好的。 四、ThreadLocal 导致的内存泄露 此处资料来源于网络资源及书籍。 为什么 ThreadLocal 会导致内存泄露?原因就是,在 ThreadLocal 中存储对象时,对于 key 值采用了 WeakReference 对象(PS: JVM中存在 强、弱、软、虚 四种引用),然后再插入到数组中。所以,当把 ThreadLocal 实例设置为 null 时,ThreadLocal 将会被 GC 回收。但是,value 却无法被回收,因为存在了一条 current Thread 的强引用,这就会导致内存泄露。一直持续到 Thread 结束,所有资源才能被回收。 可能这个时候有人认为没事……只要线程被销毁就没事了……那么我们假设在系统中使用 线程池技术 呢,线程结束后是不会被销毁的将会再次使用…………这样子导致内存泄露的问题将是致命的! 如何避免内存泄露?既然找到了内存泄露的原因,找到其解决方案就不难了。只要在使用完 ThreadLocal 后及时调用 remove() 方法清除不要的 Entry 对象,就可以避免此个问题了。","categories":[{"name":"Java 学习笔记","slug":"Java-学习笔记","permalink":"https://zggdczfr.cn/categories/Java-学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"},{"name":"多线程","slug":"多线程","permalink":"https://zggdczfr.cn/tags/多线程/"}]},{"title":"Java反射机制","slug":"Java反射机制","date":"2018-01-17T06:14:09.000Z","updated":"2018-01-17T06:18:06.195Z","comments":true,"path":"2018/01/17/Java反射机制/","link":"","permalink":"https://zggdczfr.cn/2018/01/17/Java反射机制/","excerpt":"Java 反射机制笔记","text":"Java 反射机制笔记 Java 反射机制一、前言Java 的反射机制是一个非常强大的功能。最近在看 Spring 源码,频繁看到反射的身影。因此记录一下以便以后复习使用。 Java 反射机制 官方文档 Quick Start。 二、反射基础1、Java 反射机制定义 Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 JVM 包括两种编译: 静态编译:在编译时确定类型,绑定对象。 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。(反射机制) Java反射机制主要提供了以下功能: 获取一个对象的类信息。 获取一个类的访问修饰符、成员、方法、构造方法以及超类的信息。 检查获取属于一个接口的常量和方法声明。 创建一个直到程序运行期间才知道名字的类的实例。 获取并设置一个对象的成员,甚至这个成员的名字是在程序运行期间才知道, 检测一个在运行期间才知道名字的对象的方法。 2、理解 Class 和类获取一个对象对应的反射类Class:在Java中有三种方法可以获取一个对象的反射类。 举个栗子:这三种方法输出的都是同一类对象。结果为java.lang.String。 通过 .class 12Class class1 = String.class;System.out.println(class1.getName()); 通过 getClass 方法 12Class class2 = \"String\".getClass();System.out.println(class2.getName()); 通过 forName 方法 12Class class3 = Class.forName(\"java.lang.String\");System.out.println(class3.getName()); 三、Java 反射相关操作1、API 操作 获得成员方法 Method 获得成员变量 Field 获得构造函数 Constructor 先定义一个被反射对象 C: 1234567891011121314151617class C { public C(){} // 构造函数 Constructor private C (String Constructor){ System.out.println(\"执行私有构造方法:\" + Constructor); } // 成员变量 Field private String priStr = \"private string field\"; public String pubStr = \"public string\"; protected String proStr = \"protected string field\"; String defStr = \"default string field\"; // 成员方法 Method public void g() { System.out.println(\"public C.g()\"); } protected void v () { System.out.println(\"protected C.v()\"); } void u() { System.out.println(\"package C.u()\"); } private void w() { System.out.println(\"private C.w()\"); }} 注意,这些方法、属性的权限都是不一样的。在 Java 反射机制中,不管是public,default,protect还是private方法,通过反射类我们都可以自由调用。 测试调用: 12345678910111213141516171819202122232425262728public static void main(String[] args) { try { Class clazz = Class.forName(\"com.qg.fangrui.AlgorithmDemo.bsaedata.myreflect.C\"); Object obj = clazz.newInstance(); System.out.println(\"=== 获得成员方法 Method ===\"); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods){ method.setAccessible(true); // 设置 private 可访问,若是访问 public 则不需要 System.out.println(method.getName()); method.invoke(obj); } System.out.println(\"=== 获得成员变量 Field ===\"); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); // 同理 System.out.println(field.get(obj)); } System.out.println(\"=== 获得构造函数 Constructor ===\"); // Class的newInstance方法,只能创建只包含无参数的构造函数的类 // 带参数的构造函数,得用 Class.getDeclaredConstructor(String.class) Constructor constructor = clazz.getDeclaredConstructor(String.class); constructor.setAccessible(true); constructor.newInstance(\"Constructor\"); // 同理 } catch (InvocationTargetException| IllegalAccessException | ClassNotFoundException | InstantiationException | NoSuchMethodException e) { e.printStackTrace(); }} 实验结果: 12345678910111213141516=== 获得成员方法 Method ===upackage C.u()vprotected C.v()wprivate C.w()gpublic C.g()=== 获得成员变量 Field ===private string fieldpublic stringprotected string fielddefault string field=== 获得构造函数 Constructor ===执行私有构造方法:Constructor 2、利用动态代理实现面向切面编程利用动态代理技术只能够代理接口。 先创建一个接口以及其实现: 12345678910111213141516// 代理接口public interface ClientInterface { public void client();}// 实际代理对象public class Client implements ClientInterface { public void client(){ System.out.println(\"=== 开始逻辑处理 ===\"); try { Thread.sleep(2000); // 模拟逻辑处理 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(\"=== 结束逻辑处理 ===\"); }} 创建代理接口以及实现类 123456789101112131415161718// 代理接口类,模拟 Spring 中的 Advice 接口public interface Advice { public void before(); // 前置代理 public void after(); // 后置代理}// 代理实现类public class TimeAdvice implements Advice { long startTime, endTime; @Override public void before() { startTime = System.currentTimeMillis(); // 记录开始时间 } @Override public void after() { endTime = System.currentTimeMillis(); // 记录结束时间 System.out.println(\"=== 执行时间:\" + (endTime - startTime) + \" ===\"); }} 代理对象类: 123456789101112131415161718192021222324public class SimpleProxy implements InvocationHandler{ private Object obj; private Advice advice; // 指定代理对象 public Object bind(Object obj, Advice advice){ this.obj = obj; this.advice = advice; return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this); } // 实现代理 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { advice.before(); result = method.invoke(obj, args); advice.after(); } catch (Exception e){ e.printStackTrace(); } return result; }} 客户端调用: 1234567public class Test { public static void main(String[] args) { SimpleProxy proxy = new SimpleProxy(); ClientInterface client = (ClientInterface) proxy.bind(new Client(), new TimeAdvice()); client.client(); }} 3、利用反射实现泛型擦除 Java 中集合的泛型,是为了防止错误输入,只在编译阶段有效,绕过了编译到了运行期就无效了。 直接利用一个例子来实现: 1234567891011121314151617181920212223242526public class ReflectGeneric { public static void main(String[] args) { List list1 = new ArrayList(); // 没有泛型 List<String> list2 = new ArrayList<>(); // 有泛型 // 先进行正常的元素添加方式,在编译期对泛型进行检查 list2.add(\"hello\"); System.out.println(\"List2 的长度为 \" + list2.size()); /* 通过反射来实现泛型擦除: 1、先获得反射的类; 2、通过方法反射绕过编译器来调用add方法 */ Class clazz1 = list1.getClass(); Class clazz2 = list2.getClass(); System.out.println(\"检查两个类的类型是否相同:\" + (clazz1 == clazz2)); try { Method method = clazz2.getMethod(\"add\", Object.class); // 反射得到add方法 method.invoke(list2, 20); System.out.println(\"List2 的长度为 \" + list2.size()); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } }} 结果: 123List2 的长度为 1检查两个类的类型是否相同:trueList2 的长度为 2 综上可知: 在编译期的时候,泛型会限制集合内元素类型保持一致。但是进入运行期以后,泛型就不再起作用了,通过反射调用添加方法,即使是不同类型的元素也可以插入集合。(这也是 Java 泛型语法糖带来的影响) 四、参考资料 Java反射机制应用实践","categories":[{"name":"Java 学习笔记","slug":"Java-学习笔记","permalink":"https://zggdczfr.cn/categories/Java-学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"},{"name":"反射机制","slug":"反射机制","permalink":"https://zggdczfr.cn/tags/反射机制/"}]},{"title":"Java中的代理","slug":"Java中的代理","date":"2018-01-17T05:55:04.000Z","updated":"2018-01-17T06:19:44.092Z","comments":true,"path":"2018/01/17/Java中的代理/","link":"","permalink":"https://zggdczfr.cn/2018/01/17/Java中的代理/","excerpt":"Java 三种代理的简单记录","text":"Java 三种代理的简单记录 Java 中的代理一、前言 代理模式是一种很常见的设计模式,尤其在 Spring 等框架中广泛应用。在 Java 中,一般有三种方法实现代理: 静态代理 JDK 实现动态代理 CGLIB 实现动态代理 首先,什么是代理? 在某些情况下,我们不希望或者不能直接访问对象A,而是通过访问一个中介对象B,再由B去访问A来达到目的,这种方式就叫做代理。 代理的优点: 隐藏委托的实现(显而易见)。 解耦,不改变委托类代码情况下,做一些额外的处理,比如添加初始判断等。 二、静态代理 静态代理就是代理类在程序运行前已经存在的代理方式。 举个栗子:Class B 中有个 Class A 的实例,通过调用 B 的方法就可以调用 A 的方法,所以说 A 是被代理类(委托类),B 是代理类。 下面实现一个静态代理demo: 定义一个接口 Target 并实现该接口: 1234567891011public interface Target { public String execute();}// 被代理类public class TargetImpl implements Target { @Override public String execute() { System.out.println(\"TargetImpl execute!\"); return \"execute\"; }} 创建一个代理类: 1234567891011121314151617181920212223// 代理类public class proxy implements Target { private Target target; public proxy(Target target) { this.target = target; } @Override public String execute() { before(); String result = this.target.execute(); after(); return result; } private void before(){ System.out.println(\"before\"); } private void after(){ System.out.println(\"after\"); }} 创建一个测试类: 1234567public class ProxyTest { public static void main(String[] args) { Target target = new TargetImpl(); proxy proxy = new proxy(target); System.out.println(proxy.execute()); }} 测试结果: 1234beforeTargetImpl execute!afterexecute 静态代理实现简单易懂,但需要针对被代理的方法提前写好代理类。如果需要代理的对象非常多的情况下,会增加许多重复的代码工作。 三、JDK 实现动态代理 代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理。 JDK 实现动态代理主要是通过反射机制(java.lang.reflect.Proxy),在运行时动态生成代理类。这种方法方便对代理函数做统一或特殊处理,如记录所有函数的执行时间。 12345678910111213141516171819202122// JDK 代理类public class JDKDynamicProxyHandler implements InvocationHandler { private Target target; public JDKDynamicProxyHandler(Target target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); after(); return result; } private void before(){ System.out.println(\"=== before ===\"); } private void after(){ System.out.println(\"=== after ===\"); }} 测试类: 1234567891011public class JDKDynamicProxyTest { public static void main(String[] args) { // 这里调用了静态代理的被代理对象 Target target = new TargetImpl(); JDKDynamicProxyHandler handler = new JDKDynamicProxyHandler(target); Target proxySubject = (Target) Proxy.newProxyInstance(TargetImpl.class.getClassLoader(), TargetImpl.class.getInterfaces(),handler); String result = proxySubject.execute(); System.out.println(result); }} 结果: 1234=== before ===TargetImpl execute!=== after ===execute 但是,无论是 JDK 实现动态代理还是静态带领,都需要定义接口,然后才能实现代理功能。这同样存在局限性。 四、CGLIB实现动态代理 CGLIB 采用了底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。 123456789101112131415161718192021222324public class CglibProxy implements MethodInterceptor { private Object target; public Object getProxyInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); enhancer.setCallback(this); // call back method return enhancer.create(); // create proxy instance } @Override public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { before(); Object result = proxy.invokeSuper(target, args); after(); return result; } private void before() { System.out.println(\"=== before ===\"); } private void after() { System.out.println(\"=== after ===\"); }} 调用测试类: 12345678public class CglibTest { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); Target hello = (Target) proxy.getProxyInstance(new TargetImpl()); String result = hello.execute(); System.out.println(result); }} 结果: 1234=== before ===TargetImpl execute!=== after ===execute 代理对象的生成过程由Enhancer类实现,大概步骤如下: 生成代理类Class的二进制字节码; 通过Class.forName加载二进制字节码,生成Class对象; 通过反射机制获取实例构造,并初始化代理类对象。 五、Spring AOP 原理 两种动态代理的区别: Java JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。 而 CGLIB 动态代理是利用 asm 开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。 JDK动态代理与CGLIB动态代理均是实现Spring AOP的基础。 Spring AOP 动态代理策略:(来源于《Spring 源码深度解析》) 如果目标对象实现了接口,默认情况下会采用 JDK 动态代理实现 AOP。 如果目标对象实现了接口,可以强制使用 CGLIB 实现 AOP。 如果目标对象没有实现接口,必须采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 之间转换。 六、参考资料 来源于网上资源","categories":[{"name":"Java 学习笔记","slug":"Java-学习笔记","permalink":"https://zggdczfr.cn/categories/Java-学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"},{"name":"代理模式","slug":"代理模式","permalink":"https://zggdczfr.cn/tags/代理模式/"}]},{"title":"远方的家,或者是远方的未来?","slug":"远方的家,或者是远方的未来?","date":"2017-08-16T05:57:41.000Z","updated":"2017-08-16T06:10:15.632Z","comments":true,"path":"2017/08/16/远方的家,或者是远方的未来?/","link":"","permalink":"https://zggdczfr.cn/2017/08/16/远方的家,或者是远方的未来?/","excerpt":"人,总会有负面的时候。同时好久没记录一下自己的心情,就顺手记录一下。","text":"人,总会有负面的时候。同时好久没记录一下自己的心情,就顺手记录一下。 远方的家,或者是远方的未来?前言原本昨天办公室停电了,搞得自己一天什么事情都没怎么做。心中非常过意不去,专门等到电力恢复了跑到办公室。最近一段时间来,各种添乱的事情频出,搞得自己心中也很浮躁。今晚,好像又爆出了一点破裂的危机,也许是出自于自己性格,也或许是因为对于责任的解读方式,或者是因为对于未来的不信任感,我在迷茫,也在逃避着。 先是学院分配的工作中出了许多纰漏,虽然主要不是我的责任,但是我主导的事情还搞成那样子还是有我的领导不利之责。 其次,自己负责的研发工作进展不大,其实还是我分配太少时间在上面,这一点还是我的错。 还想着昨晚打个电话回家听听妈妈的声音,等到想起来发现已经11点了,不能打扰到家人。 看到了师弟问我这个暑假回家多少天,突然间觉得心中有点愧疚。 最后,真的是因为种种事情,今晚突然间就矫情了。 人,总会有负面的时候。同时好久没记录一下自己的心情,就顺手记录一下。 远方的家也许,对于家中的父母来说,自从大学后我就一直离他们很远。家,对于我来说,就在远方。大学生涯,好像只能有一次完整的(大一上)暑假待在家。上个暑假,加入了团队必须留在学校学习,回家3+5天;上个寒假,还是待在学校好久才回家;这个暑假,因为团队还是留在学校,同样的回家5天,还是专门回去给爷爷过生日……这个寒假,将是我最重要的阶段,我需要在这个寒假来临之前,确定好我是选择读研还是实习!下个、下下个暑假/寒假,很有可能就是在实习或者是准备考研之类的。感觉我,好像成了家里的过客。 还记得,回家那天匆匆赶回去,妈妈显得很高心,还说了一大堆最近几天准备给我买什么好吃的计划,我只能傻傻地在那里微笑着装傻。还记得,离开的前一天晚上,妈妈突然问我能不能向老师请假之类,再多留几天,我很坚持地用“过几天要去澳门交流一周”的理由回绝了,爸妈也没有说什么。也许对于父母来说,他们绝不会耽误孩子自己规划的道路吧。我自认为我不是一个恋家的人,或者套用一句基友的话:我这个人,有点冷酷,甚至无情吧。在家不到5天的时间内,我没有去见任何一个老友,来大学前认识的同学中理论上就只有一个,知道我回来了。这段时间内,我基本都是待在家里,陪着父母,也算是一种对于父母的自我安慰吧。在准备开往广州的高铁上,我在一个关系很好的微信群中发了一句“已经不止一次自己一个人乘坐高铁回家”。突然间我有了一种感觉,这个以往有许多美好记忆的地方,好像渐渐在记忆里褪色了……回来了,好像就只有父母还有美食了,我也不清楚为什么我有这种感觉。家,好像成了远方。当新的学期来临时,我应该会更忙,会有更多的计划需要我去完成,不仅仅有自己的计划、也有责任的计划和锻炼的计划。那种一年回家寥寥几次,每次匆匆留下几天的情况,可能会成为一种习惯了!### 远方的未来前几天,在朋友圈一篇推文中看到一句话,顺手抄在桌面便利贴处:我不想把年轻的时光,都浪费在工作上,我要把生命浪费在美好的事务物上。这句话来自于著名财经作家吴晓波老师的一本书。最初看到这句话的时候,我不禁在心中自嘲:你有资格说这句话吗?可能仁者见仁智者见智,有人看到的是对于青春的不后悔;可能我比较负面化,我看到更多的是一个成功人士对于人生后半期的追求,但现在的我没有资格来说这句话。昨天晚上,新华社的微信公众号推了一篇夜读《别人光鲜的背后,有你未必能吃得了的苦》(2017年8月16日)。里面有这么一句话:喜欢一件事,不仅仅是要有冲动,还要有坚持的韧劲。不是随随便便就成功的。时间从来都是匆匆不可逆。年轻时努力自我增值,老了才有能力浪费时光。否则,等精力、体力、智力、心力都衰退的时候,你只有被时光耗尽的份儿了。但,现实的残酷是,大多数人终将无法抗拒眼前各种诱惑。可能你会觉得,遵循以下从众心理,与大家差不多就行了;或者你想偷懒躲在宿舍睡觉,什么都不想干…………归结起来,就是一直在逃避付出和拒绝成长。举个例子,就像在团队中,我问我的小伙伴关于某个需求有何实现可能性技术方案,我不想听到“我怎么不知道”这类话;再往深入一点问“为什么不知道?”,我可能会得到一句“你让我干嘛就干嘛啊”……讲道理,我真的会主动清理门户。有的人是不想长大,有的人是真的长不大。 作为一个对于时间观念有点偏执的人,我会尽可能在每天清晨规划好我今天之内至少要完成什么事。不止一个同学朋友说我活得太累了,能不能换一种生活方式。我往往会笑着说,在我当前最大的目标没有达成的时候,我不会停下来的。的确每个人,都可以选择不同的生活,但我不会麻痹自己。我是家中的小儿子,一直有着爷爷奶奶、父母和三个姐姐护着,现在的话赚取自己的大学期间生活费应该也是没问题的。但我选择了留在学院的团队中,完全没有一分钱而且任务也繁重,甚至连寒暑假也快没了……实际上,我也感谢这个团队,它给我提供了许多便捷,让我认识到一批一直在努力的小伙伴们,让我有机会操作几百万的新设备,让我能够以一个较为完备的团队去参加竞赛…………说实话,我并不后悔现在的选择。 原谅我,我身边的所有人。我无法接受自己为了逃避,荒废掉人生中最能够快速成长的时光。人生就是如此,不要只看到别人如何光鲜亮丽的身影。更重要的是,我不想年过半百之时,还要早起晚归、争着时间挤地铁、为了承当巨大的房贷还在苦苦挣扎…… 回归正题,对于“远方的家”,等着我大学的目标完成,将第一时间奔回家;对于“远方的未来”哦,我心甘情愿地选择:在最美好的青春年华,玩命式的积累,只为了有朝一日能真正地担负起家中的责任。立志于梦,如今四载过半,不曾怕,也不曾悔。","categories":[{"name":"随记感悟","slug":"随记感悟","permalink":"https://zggdczfr.cn/categories/随记感悟/"}],"tags":[]},{"title":"Spring Data 简单学习","slug":"Spring-Data-简单学习","date":"2017-06-28T03:10:44.000Z","updated":"2017-06-28T03:21:52.669Z","comments":true,"path":"2017/06/28/Spring-Data-简单学习/","link":"","permalink":"https://zggdczfr.cn/2017/06/28/Spring-Data-简单学习/","excerpt":"Spring Data 的简单学习笔记~","text":"Spring Data 的简单学习笔记~ Spring Data 简单学习前言苦逼的学生党终于快迎接放假了~~~ 虽然临近期末,明明还没预习但还是想搞一些奇奇怪怪的…………最近,有一个自己的想法想在暑假搞出来以便将来可以写在简历之类的,因为学过一些Spring全家桶,就顺手练一下还有做一下学习笔记。这里的案例是为了那个项目准备的,所以会融入到那个项目中,还有我会尽可能多打注释的。另外,立个flag,暑假尽可能写博客记录一下学习生活。这一篇主要是关于 Spring Data 的学习笔记。 案例地址(欢迎大家支持给颗star):https://github.com/FunriLy/LiveChat Spring Data 介绍注意:这里的介绍来源于网络!而且,等会用到的是主要是JPA部分 看了这么多专业的介绍,其实Spring Data项目旨在为大家提供一种通用的编码模式,统一我们的API。spring Data 项目的目的是为了简化构建基于 Spring 框架应用的数据访问计数,包括非关系数据库、Map-Reduce 框架、云数据服务等等;另外也包含对关系数据库的访问支持。 Spring Data 包含多个子项目: Commons - 提供共享的基础框架,适合各个子项目使用,支持跨数据库持久化 Hadoop - 基于 Spring 的 hadoop 作业配置和一个 POJO 编程模型的 MapReduce 作业 Key-Value - 集成了 Redis 和 Riak ,提供多个常用场景下的简单封装 Document - 集成文档数据库:CouchDB 和 MongoDB 并提供基本的配置映射和资料库支持 Graph - 集成 Neo4j 提供强大的基于 POJO 的编程模型 Graph Roo AddOn - Roo support for Neo4j JDBC Extensions - 支持 Oracle RAD、高级队列和高级数据类型 JPA - 简化创建 JPA 数据访问层和跨存储的持久层功能 Mapping - 基于 Grails 的提供对象映射框架,支持不同的数据库 Examples - 示例程序、文档和图数据库 Guidance - 高级文档 看了这么多专业的介绍,其实Spring Data项目旨在为大家提供一种通用的编码模式,统一我们的API。 初学SpringData利用SpringBoot框架集合SpringData框架! 1. 资源配置pom.xml配置:123456789101112131415161718192021222324<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --></parent> <!--SpringBoot其他配置就不罗列了,主要是与Data有关的--> <!--JPA 操作数据库--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!--MySQL--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> application.properties配置:1234spring.datasource.url=jdbc:mysql://127.0.0.1:3306/XXXXXX?characterEncoding=utf8&amp;useSSL=falsespring.datasource.username=XXXXXXspring.datasource.password=XXXXXXspring.datasource.driver-class-name=com.mysql.jdbc.Driver 2. 数据库建表和Model层建立Model层建立User实体:12345678910@Entity //实体类,利用对象关系映射生成数据库表@Table(name = \"users\", schema = \"livechat\", catalog = \"\")public class User implements Serializable{ private static final long serialVersionUID = 1l; @Id private String id; @Column(nullable = false, name = \"name\") private String name;} 数据库建表:12345CREATE TABLE `users` ( `id` varchar(25) NOT NULL, `name` varchar(8) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; 3. 继承Repository接口Repository是一个标记接口,继承该接口的Bean被Spring容器识别为一个RepositoryBean: 可以使用 Query 注解查询 spring data repository 接口中的方法默认只有查询和保存,没有更新和删除的事务 SpringData的命名规范:(find | get | read) + By + field + ( + 关键字 + field )如,findByName、findByNameAndId 12345678910111213public interface UserRepositoy extends Repository<User, String> { // SQL = select * from users where name = ? public User findByName(String name); public User getById(String id); public User save(User user); /** * 关于 * ,IDEA语法报错但不影响运行,应该是IDEA本身提示的问题 * 但是有一个语法提示感觉有点别扭,所以我将它注释了 */// @Query(value = \"select * from users u where u.name = :name\", nativeQuery = true)// public User myselect(@Param(\"name\") String name);} 我将单元测试全部集中到一起了,所以将在后面进行测试。 4. 继承CrudRepository由于Repository接口的局限性,所以我一般都是用CrudRepository。但Repository可以自定义接口。CrudRepository接口继承于 Repository 接口,并新增了简单的增、删、查等方法。方法列表:1234567891011121314long count();boolean exists(Integer arg0);<S extends StudentPO> S save(S arg0);<S extends StudentPO> Iterable<S> save(Iterable<S> arg0);void delete(Integer arg0);void delete(Iterable<? extends StudentPO> arg0);void delete(StudentPO arg0);void deleteAll();StudentPO findOne(Integer arg0);Iterable<StudentPO> findAll();Iterable<StudentPO> findAll(Iterable<Integer> arg0); UserDao.java:123public interface UserDao extends CrudRepository<User, String> { //可以用 @Query 注解添加自己的事务方法} 5. 单元测试因为案例用的是SpringBoot 1.5.x,JUnit单元测试优化,并且换注解@SpringApplicationConfiguration(Application.class)为@SpringBootTest。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTestpublic class UserDaoTest { @Autowired private UserRepositoy repositoy; @Autowired private UserDao userDao; @Test public void test_reposity(){ //先在数据库中添加一条记录{id='123456', name='funrily'} String name = \"funrily\"; System.out.println(\"====测试Reposity===\"); User user = repositoy.findByName(\"funrily\"); System.out.println(user.toString());// User next = repositoy.myselect(\"funrily\");// System.out.println(next.toString());//// repositoy.save(new User(\"654321\", \"fangrui\"));// next = repositoy.findByName(\"fangrui\");// System.out.println(next.toString()); } /** * 查询表种记录的数量 */ @Test public void test_count(){ long cout = userDao.count(); System.out.println(\"count = \" + cout); } @Test public void test_exists(){ //根据主键判断是否存在 boolean isExist = userDao.exists(\"123456\"); System.out.println(\"IsExist :\" + isExist); } @Test public void test_save(){ //单个保存 User user = new User(\"127.0.0.1\", \"host\"); System.out.println(userDao.save(user)); //批量保存 User user1 = new User(\"1\", \"1\"); User user2 = new User(\"2\", \"2\"); User user3 = new User(\"3\", \"3\"); User user4 = new User(\"4\", \"4\"); List<User> users = new ArrayList<>(); users.add(user1); users.add(user2); users.add(user3); users.add(user4); System.out.println(\"批量保存:\" + userDao.save(users)); } @Test public void test_find(){ //通过id查询一个记录 User user = userDao.findOne(\"127.0.0.1\"); System.out.println(\"通过id查找一个记录:\" + user); //根据id列表查询,其内部实际上使用了 in 关键字 List<String> idList = new ArrayList<>(); idList.add(\"1\"); idList.add(\"2\"); System.out.println(\"根据id列表查询:\" + userDao.findAll(idList)); //查找所有的记录 System.out.println(\"所有的记录:\" + userDao.findAll()); } /** * 关于delete的单元测试需要在上面的单元测试通过后 * 数据库中有相应的记录才可以执行成功 */ @Test public void test_delete(){ //通过id删除一条记录 userDao.delete(\"127.0.0.1\"); System.out.println(\"通过id删除一条记录后,记录是否存在:\" + userDao.exists(\"127.0.0.1\")); //删除某个特定的记录 User user = userDao.findOne(\"123456\"); //查找一个记录 if (user != null && user.getId() != null && !user.getId().equals(\"\")){ userDao.delete(user); System.out.println(\"通过id删除一条记录后,记录是否存在:\" + userDao.exists(\"123456\")); } //根据id列表删除记录 List<User> userList = new ArrayList<>(); userList.add(userDao.findOne(\"1\")); userList.add(userDao.findOne(\"2\")); userDao.delete(userList); //删除所有的记录 userDao.deleteAll(); }} 补充点 @Query 注解使用@Query注解可以自定义JPQL语句,语句可以实现更灵活的查询。 @Modifying 注解可以通过自定义的JPQL来完成update和delete操作。在@Query注解中编写JPQL语句,但必须使用@Modify进行修饰,主要功能是通知SpringData,这是一个Update或者Delete。 Spring-data的能力远不止本文提到的这些,这里的使用只是介绍了常见的使用方式。诸如@Modifying操作、分页排序、原生SQL支持以及与Spring MVC的结合使用等等内容,我了解还不够,如果以后有时间再来补坑吧~ 参考资料http://blog.csdn.net/z69183787/article/details/30265243http://blog.csdn.net/luckyzhoustar/article/details/49362951","categories":[{"name":"spring data","slug":"spring-data","permalink":"https://zggdczfr.cn/categories/spring-data/"}],"tags":[{"name":"spring data","slug":"spring-data","permalink":"https://zggdczfr.cn/tags/spring-data/"}]},{"title":"线程复用:线程池笔记","slug":"线程复用-线程池笔记","date":"2017-04-04T15:09:29.000Z","updated":"2017-04-04T15:18:21.741Z","comments":true,"path":"2017/04/04/线程复用-线程池笔记/","link":"","permalink":"https://zggdczfr.cn/2017/04/04/线程复用-线程池笔记/","excerpt":"《实战Java高并发程序设计》学习笔记(三) : JAVA线程池笔记。主要记录了一些Java线程池的基础知识。","text":"《实战Java高并发程序设计》学习笔记(三) : JAVA线程池笔记。主要记录了一些Java线程池的基础知识。 线程复用:线程池线程池总概什么是线程池?接触过JDBC的人,一定听说过数据库连接池(比如,c3p0、Druid等)。其实在我的理解中,两者是差不多的。不过线程池中放的是线程而已。线程是一种轻量级工具,但其创建与关闭都需要花费一定的时间。而且大量的线程会抢占内存资源。盲目的大量资源会对系统造成极大的压力。线程池,中有一定数量的活跃线程。创建线程变成了从线程池中获得空闲线程;关闭线程变成了向线程池归还线程。 JDK对于线程池的支持Java通过Executors提供五种线程池,分别为: newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。 newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 newSingleThreadScheduledExcutor创建单线程化的线程池,支持定时及周期性任务执行。 线程池的使用首先是简单使用,这个没有什么特殊之处。只需记得newFixedThreadPool创建的是定长的线程池,可控制线程最大并发数,超出的线程会在队列中等待。而newCachedThreadPool创建的线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。 定时任务newScheduledThreadPool支持定时及周期性任务执行,查看了其源码,主要有以下三种方法: schedule():在给定时间,对任务进行调度; scheduleAtFixedRate() 和 scheduleWithFixedDelay():对任务进行周期性调度,但两者有所区别。 scheduleAtFixedRate() 和 scheduleWithFixedDelay() 的区别 两种调度的区别: FixedRate 方式:以上一个任务开始执行时间为起点,在之后的延迟时间后,调用下一次任务。 FixedDelay 方式:上一个任务结束后,再经过延迟时间进行任务调度。 若任务执行时间超过调度时间, FixedRate 方式:若调度时间过短,那么任务会在上一个任务结束后立刻调用(不会出现任务堆叠的现场)。 FixedDelay 方式:会严格按照任务间隔时间 = 调度时间 + 任务执行时间。 如果任务遇到异常,那么后续的所有子任务都会停止调度。因此必须保证,异常被及时处理,为周期性任务的稳定调度提供条件。 关于线程池的记录拒绝策略创建线程池的核心类 ThreadPoolExecutor 有一个参数指定了拒绝策略。拒绝策略,是系统超负荷运行时的补救措施,通常是由于压力太大而引起的,也就是线程池中的线程已经用完了且等待队列已经排满了。JDK 提供了四种拒绝策略: AbortPolicy 策略:直接抛出异常,阻止系统正常工作。 CallerRnsPolicy 策略:只要线程池未关闭,将直接在调用者线程中运行被丢弃的任务。这种做法不会真的丢弃任务,但是任务提交线程的性能将急剧下降。 DiscardOldestPolicy 策略:丢弃最老的一个请求,也就是即将被执行的任务(处于等待队列的队头),并尝试再次提交当前任务。 DiscardPolicy 策略:直接丢掉无法处理的任务。 自定义策略:自己扩展 RejectedExecutionHandler 接口。 线程扩展ThreadPoolExecutor是一个可扩展的线程池,有beforeExecute()、afterExecute()和terminated()能够对线程进行控制。123protected void beforeExecute(Thread t, Runnable r) { }protected void afterExecute(Runnable r, Throwable t) { }protected void terminated() { } 这是三个protected的空方法,摆明了可以让子类扩展。 在执行任务的线程中将调用beforeExecute和afterExecute等方法,在这些方法中还可以添加日志、计时、监视或者统计信息收集的功能。 无论是正常运行,还是抛出异常,都会调用afterExecute。但是,如果抛出Eorror,将不会调用该方法;或者beforeExecute抛出一个RuntimeException,则任务将不被执行,即该方法也不会被调用。 关于terminated,在线程池完成关闭时(就是在所有任务已经完成且所有工作者线程已经关闭),用来释放Executor在生命周期里分配的各种资源,此外还能执行信息通知、日志记录等功能。 补充 使用线程池被”吃”掉了异常堆栈信息在使用线程池提交线程时,可能会发生异常堆栈信息被”吃”掉的现象,而解决方法: 放弃submit(),改用execute()。 获取submit()方法返回类的get()方法。 12Future future = pools.submit(new Thread());future.get(); 扩展 ThreadPoolExecutor 线程池,让其在调度任务前,先保存提交任务线程的堆栈消息(就是重写线程池线程的调用方法)。 自定义线程:ThreadFactory这个接口只有一个方法 newThread(Runnable r),主要是由线程池调用新建线程。 优化线程池线程数量在《Java Concurrency in Practice》书中给了一个估算线程池大小的经验公式: 12345Ncpu = CPU数量Ucpu = 目标CPU的使用率,0 <= Ucpu <= 1W/C = 等待时间与计算时间的比率所以,最优的线程池大小为:Nthreads = Ncpu * Ucpu * ( 1 + W/C ) 同时,在Java中,可以通过Runtime.getRuntime().availableProcessors获取可用的CPU数量。 参考资料 《实战Java高并发程序设计》(葛一鸣 郭超 著)","categories":[{"name":"高并发学习笔记","slug":"高并发学习笔记","permalink":"https://zggdczfr.cn/categories/高并发学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"}]},{"title":"多线程的同步控制","slug":"多线程的同步控制","date":"2017-03-30T11:48:12.000Z","updated":"2017-03-30T11:50:55.241Z","comments":true,"path":"2017/03/30/多线程的同步控制/","link":"","permalink":"https://zggdczfr.cn/2017/03/30/多线程的同步控制/","excerpt":"《实战Java高并发程序设计》学习笔记(二) : 多线程的同步控制。主要记录了一些Java有关多线程的同步控制的知识。","text":"《实战Java高并发程序设计》学习笔记(二) : 多线程的同步控制。主要记录了一些Java有关多线程的同步控制的知识。 多线程的同步控制 一、重入锁 重入锁完全可以代替 synchronized 关键字。在JDK 1.5 早期版本,重入锁的性能优于 synchronized。JDK 1.6 开始,对于 synchronized 做了大量优化,使得两者性能差距不大。 代码示例:12345678910 private static ReentrantLock lock = new ReentrantLock(); @Override public void run() { lock.lock(); // 加锁 try { // doSomething } finally { lock.unlock(); // 释放锁 } } PS:因为重入锁需要我们手动加锁/释放锁,比较好实现对于逻辑的控制。 注意事项: 加锁后记得释放锁,否则其他线程就没有机会访问(比如,临界区资源)。 一个线程多次获得锁(其实是同一把锁)。但加了多少次锁,就要释放多少次锁。同时,lock()方法会一直堵塞线程直到释放锁。 中断响应 : lockInterruptibly()方法是一个可以对中断进行响应的锁申请动作(在等待锁的过程中,可以响应中断)。 锁申请等待限时 : tryLock()方法给定一个等待时间,让线程自动放弃等待,避免死锁(PS:当前线程会尝试获得锁,如果锁未被其他线程占用,则申请成功返回true;否则返回false)。 公平锁: 通过ReentrantLock(true)的构造函数来启用公平锁。 按申请时间顺序来获得锁,不会产生饥饿现象。 系统需要维护一个有序队列,实现成本较高且性能相对低下(一般情况下使用非公平锁)。 ReentrantLock的几个重要方法 lock():获得锁,如果锁已经被占用,则等待。 lockInterruptibly():获得锁,但优先响应中断。 tryLock():立刻/给定时间内尝试获得锁(可以开启公平锁)。 unlock():释放锁。 二、Condition条件Condition 与重入锁相关联。利用 Condition 可以让线程在合适的时间内等待,或者在某一个特定的时刻得到通知,继续执行。调用代码如下:12 private static ReentrantLock lock = new ReentrantLock(); private static Condition condition = lock.newCondition(); 常用的方法: await() 当前线程等待,同时释放锁。其他线程可以使用 signal() 或 signalAll() 方法使线程获得锁并继续执行。或者线程中断时,也能跳出等待。 awaitUninterruptibly() 方法与 await() 方法基本相同,,但等待过程中不会响应中断。 signal() 方法唤醒一个在等待中的线程;signalAll() 方法唤醒所有在等待中的线程。 用一个实例来理解 Condition 对象代码如下:12345678910111213141516171819202122232425262728public class MyThread implements Runnable { private static ReentrantLock lock = new ReentrantLock(); //定义重入锁 private static Condition condition = lock.newCondition(); //定义Condition @Override public void run() { try { lock.lock(); //调用 await() 需要先获得锁 condition.await(); System.out.println(\"这条线程继续执行!\"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); Thread t1 = new Thread(myThread); t1.start(); Thread.sleep(2000); //通知 t1 线程继续执行 lock.lock(); //调用 signal() 需要先获得锁 condition.signal(); lock.unlock(); //记得释放锁,不然线程没有继续执行 }} 首先主线程开启副线程(t1)后,休眠2000毫秒。t1线程执行 run() 方法:获得锁后,调用 await() 释放锁并进入等待状态。主线程被唤醒后,同样需要是获得锁,调用 signal() 唤醒线程但没有释放锁,所以要主动释放锁将其谦让给被唤醒的线程。一旦线程被唤醒了,它会尝试着获取与之绑定的重入锁,成功获取后就可以继续执行。 在JDK内部,重入锁和 Condition 对象被广泛地使用。 三、允许多个线程同时访问:信号量(Semaphore)信号量是对锁的扩展,可以指定多个线程同时访问某个资源 在构造信号量对象时,必须指定信号量的准入数。 申请信号量使用 acquire() 操作,离开时必须使用 release() 释放信号量(PS:若申请了但没有释放,会使可以进入临界区的线程数量越来越少,直到所有的线程均不可访问)。 四、倒计时器 CountDownLatch这个工具用来控制线程等待,可以让某个线程等待直到倒计时结束,再执行。(PS:就像王者农药一样,需要所有玩家都准备好了才能开始游戏)代码示例(来源于《实战Java高并发程序设计》,为了便于理解做了一点修改):1234567891011121314151617181920212223242526272829303132public class MyThread implements Runnable { private static final CountDownLatch end = new CountDownLatch(5); private static final MyThread MY_THREAD = new MyThread(); private static int count = 1; @Override public void run() { try { Thread.sleep(new Random().nextInt(5) * 1000); System.out.println(\"第 \" + count + \"条线程结束\"); count++; //倒计时减一 end.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(5); for (int i=0; i<5; i++){ //线程池加入线程 executorService.submit(MY_THREAD); } System.out.println(\"主线程开始执行\"); //主线程等待倒计时结束 end.await(); System.out.println(\"主线程重新执行\"); //关闭线程池 executorService.shutdown(); }} 注意注意: 这里的ExecutorService是线程池,在后面的学习中是非常重要的工具(有关其用法以后会学到,这里提前了解一下)。 这里的 count 其实是线程不安全的,但因为并发量不高影响不大,而且 count 变量只是为了展示而已,其意义也不大。 输出结果如下:1234567主线程开始执行第 1条线程结束第 2条线程结束第 3条线程结束第 4条线程结束第 5条线程结束主线程重新执行 五、线程阻塞工具:LockSupportLockSupport 是用来创建锁和其他同步类的基本线程阻塞,其特点: 可以在线程内任意位置堵塞线程。 不需要想获得对象的锁就可以执行;不会抛出 InterruptedException 异常。 park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程(PS:不会引起 suspengd() 与 resume() 造成的死锁问题)。 park() 方法支持中断,但不会抛出异常。所以需要我们使用 Thread.interrupted() 等方法来获得中断的标记。 为什么 park() 和 unpark() 不会引起 suspengd() 与 resume() 造成的死锁问题?答:首先,LockSupport 采用了类似于信号量的许可证机制。它为每一个线程准备了一个许可证(有且只有一个)。若 park() 可以获得许可证,会立刻返回,否则将处于堵塞状态。而 unpark() 会使一个许可证变为可用状态。所以说,即使 unpark() 发生在 park() 之前,也可以保证下一个 park() 操作立刻返回。其次,处于 park() 挂起状态的线程,会明确地给出一个WAITING状态,还会标记是 park() 引起的。 六、其他 ReadWriteLock 读写锁:就是读读之间不阻塞(如果读操作次数远远大于写操作,读写锁就可以发挥最大功效,提升系统性能)。 CyclicBarrier 循环栅栏:是 CountDownLatch 的升级版,可以接收一个参数 barrierAction,代表着一次计数完成后系统会执行的动作。 七、参考资料 《实战Java高并发程序设计》(葛一鸣 郭超 著)","categories":[{"name":"高并发学习笔记","slug":"高并发学习笔记","permalink":"https://zggdczfr.cn/categories/高并发学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"}]},{"title":"JAVA并行程序基础","slug":"JAVA并行程序基础","date":"2017-03-30T11:13:34.000Z","updated":"2017-03-30T11:47:27.136Z","comments":true,"path":"2017/03/30/JAVA并行程序基础/","link":"","permalink":"https://zggdczfr.cn/2017/03/30/JAVA并行程序基础/","excerpt":"《实战Java高并发程序设计》学习笔记(一) : JAVA并行程序基础。主要记录了一些Java并行程序的基础知识。","text":"《实战Java高并发程序设计》学习笔记(一) : JAVA并行程序基础。主要记录了一些Java并行程序的基础知识。 JAVA并行程序基础 一、有关线程你必须知道的事进程与线程 在等待面向线程设计的计算机结构中,进程是线程的容器。我们都知道,程序是对于指令、数据及其组织形式的描述,而进程是程序的实体。 线程是轻量级的进程,是程序执行的最小单位。(PS:使用多线程去进行并发程序的设计,是因为线程间的调度和切换成本远小于进程) 线程的状态(Thread的State类): NEW–刚刚创建的线程,需要调用start()方法来执行线程; RUNNABLE–线程处于执行状态; BLOCKED–线程遇到synchronized同步块,会暂停执行直到获得请求的锁; WAITING–无限制时间的等待; TIMED_WAITING–有限制时间的等待; TERMINATED–执行完毕,表示结束。 二、线程的基本操作创建线程代码示例:start()方法会新建线程并调用run()方法!12Thread t = new Thread();t.start(); 注意以下代码:这段代码也可以编译执行,但不能新建一个线程,而是在当前线程中调用run()方法。(PS:就是作为一个普通方法来调用)12Thread t = new Thread();t.run(); 不能用run()来开启线程,它只会在当前线程中串行执行run()方法。 另外,Thread类有一个非常重要的构造方法:public Thread(Runnable target)这个构造方法在start()调用时,新线程会执行Runnable.run()方法。实际上,默认的Thread.run()也是这样子做的。1234567891011public class MyThread implements Runnable { public static void main(String[] args) { Thread t = new Thread(new MyThread()); t.start(); } @Override public void run() { System.out.println(\"开启新线程执行run()方法\"); }} 继承Thread的方式定义线程后,就不能在继承其他的类了,导致程序的可扩展性大大降低。而且重载run()方法也只是和普通方法一样,不会被JVM主动调用。 使用实现Runnable接口并传入实例给Thread,可以避免重载Thread.run(),单纯使用接口来定义Thread。 终止线程JAVA提供了一个stop()方法来关闭线程,不过这是一个被标注为废弃的方法。原因是stop()方法太过暴力,强行将执行到一半的线程关闭可能导致一些数据不一致的问题。 一帮情况下,不要使用 stop() 方法! 解决方法:定义一个标记变量stopme,用于指示线程是否需要退出。 线程中断在JAVA中,为了完善线程安全退出,有一种重要的线程协作机制————线程中断。(注意:线程中断会给线程发送一个通知,但线程接到通知后如何处理,则由目标线程自行决定) 线程中断的三个重要方法 123 public void Thread.interrupt() //中断线程 public boolean Thread.isInterrupted() //判断线程是否被中断 public static boolean Thread.interrupted() //判断线程是否被中断,并清除当前中断状态 Thread.interrupt()方法中断,线程不会立刻停下来,比如死循环体。可以通过isInterrupted()方法来判断是否跳出循环体。 线程休眠 Thread.sleep()让当前线程休眠若干时间,其签名如下:public static native void sleep(long millis) throws InterruptedException InterruptedException不是运行时异常,当线程处于休眠状态时,如果被中断就会抛出这个异常。 sleep()方法由于中断抛出异常时,会清除中断标记。所以,在异常处理中需要再次设置中断标记! Thread.wait()与notify()这两个方法是配套的,一个线程调用obj.wait()处于等待状态,需要其他线程调用obj.notify()来唤醒。obj对象就是多个线程间有效通信手段。 Object.wait() 必须包含在 synchronzied 语句中,无论是 wait() 还是 notify() 都需要先获得目标对象的一个监视器。(PS:这两个方法都是执行后立刻释放监视器,防止其他等待对象线程因为该线程休眠而全部无法正常执行) Object.wait() 同样会抛出InterruptedException异常。 Object.notifyAll() 方法会唤醒等待队列中所有等待的线程。 suspengd()与resume()前者是线程挂起,后者是继续执行,这是一对互相配合的方法。这两个也是已经被废弃的方法,了解一下就行了。 suspend() 会导致休眠线程的所有资源都不会被释放,直到执行 resume() 方法。(如果两个方法,后者执行于前者之前,则线程很难有机会被继续执行) 对于被挂起的线程,其状态是Runnable,这会严重影响到我们对于系统当前状态的判断。(不可饶恕) join()与yield() join() 方法其签名如下:public final void join() throws InterruptedException;public final void join(long millis) throws InterruptedException;第一个方法表示无限等待;第二个方法表示一定时间的等待。join()方法会阻塞当前线程,直到目标线程结束,或者到达阻塞时间。同时,join()方法的本质是让在当前线程对象实例上调用线程的wait()方法。 yield() 方法其签名如下:public static native void yield();这个静态方法会使当前线程让出CPU。(PS:但还是会进行CPU资源的抢夺) 当某个线程不是那么重要,或者优先级别较低,可以在适当时候调用 Thread.yield(),给予其他重要线程更多工作机会。 关于线程操作的补充 sleep() 与 wait() 的区别Object.wait() 和 Thread.sleep() 都可以让线程等待若干时间。除了wait() 可以被唤醒外,另外一个主要区别:wait() 方法会释放目标对象的锁,而 sleep() 方法不会释放任何资源。 三、分门别类的管理:线程组简单建立一个线程组(代码来源于《实战Java高并发程序设计》书中)12345678910111213141516171819202122232425262728293031public class MyThread implements Runnable { public static void main(String[] args) { //创建一个叫\"PrintGroup\"的线程组 ThreadGroup tg = new ThreadGroup(\"PrintGroup\"); Thread t1 = new Thread(tg, new MyThread(), \"T1\"); //加入线程组 Thread t2 = new Thread(tg, new MyThread(), \"T2\"); t1.start(); t2.start(); //由于线程是动态的,activeCount()获得活动线程总数的估算值 System.out.println(\"活动线程总数 = \" + tg.activeCount()); //打印出线程组的线程信息 tg.list(); //注意这个方法与Thread.stop()方法遇到问题一样 tg.stop(); } @Override public void run() { String groupAndName = Thread.currentThread().getThreadGroup().getName() + \"---\" + Thread.currentThread().getName(); while (true) { System.out.println(\"I am \" + groupAndName); try { Thread.sleep(3000); } catch (InterruptedException e){ e.printStackTrace(); } } }} 四、驻守后台的守护线程守护线程,是系统的守护者,在后台完成系统性的服务,比如垃圾回收线程、JIT线程等。当一个Java应用内只有守护线程时,Java虚拟机就会自然退出。将线程设置为守护线程:123 Thread t = new Thread(new MyThread()); t.setDaemon(true); t.start(); 注意:设置守护线程必须在线程start()之前设置,否则会得到一个IllegalThreadStateException,但程序和线程依然可以正常执行,只是线程被当作用户线程而已。 五、线程优先级Java中线程可以有自己的优先级。由于线程的优先级调度和底层操作系统有密切关系,在各个平台表现不一,无法精准控制。在Java中,使用 1 到 10 表示线程优先级。一般可以使用三个内置的静态标量表示:123 public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10; PS:数字越大,优先级越高(一般情况)。 六、synchronized 关键字synchronized 关键字用法简单记录: 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。 直接作用于实例对象:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。 synchronized 可以保证线程间的原子性、可见性和有序性(被 synchronized 限制的多个线程是串行执行的)。 七、部分隐秘的错误记录 执行(synchronized)同步代码的两个线程指向了不同的Runnable实例,即两个线程使用了两个不同的锁。解决方法:将同步代码修改为静态(static)方法。 使用线程不安全的容器,如 ArrayList、HashMap 等。解决方法:改用线程安全的容器,如 Vector、ConcurrentHashMap。 不变对象加锁,导致对于临界区代码控制出现问题。 不变对象:对象一旦被创建,就不能修改。(如,Integer i; i++的本质是创建一个新的Integer对象,并将引用赋值给i,同时这里涉及到Integer引用的特性) 八、参考资料 《实战Java高并发程序设计》(葛一鸣 郭超 著)","categories":[{"name":"高并发学习笔记","slug":"高并发学习笔记","permalink":"https://zggdczfr.cn/categories/高并发学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"}]},{"title":"JVM学习笔记(三) 垃圾收集算法","slug":"JVM学习笔记-三-垃圾收集算法","date":"2017-03-23T08:56:12.000Z","updated":"2017-03-23T09:08:05.877Z","comments":true,"path":"2017/03/23/JVM学习笔记-三-垃圾收集算法/","link":"","permalink":"https://zggdczfr.cn/2017/03/23/JVM学习笔记-三-垃圾收集算法/","excerpt":"垃圾收集算法主要有以下几种: 标记-清除算法(Mark-Sweep)、复制算法(Copying) 和 标记-整理算法(Mark-Compact)。","text":"垃圾收集算法主要有以下几种: 标记-清除算法(Mark-Sweep)、复制算法(Copying) 和 标记-整理算法(Mark-Compact)。 垃圾收集算法 主要通过阅读《深入了解Java虚拟机》(周志明 著)和网络资源汇集而成,为本人学习JVM的笔记。同时,本文理论基于JDK 1.7版本,暂不考虑 1.8和1.9 的新特性,但可能初略提到。 垃圾收集算法主要有以下几种: 标记-清除算法(Mark-Sweep)、复制算法(Copying) 和 标记-整理算法(Mark-Compact)。 标记-清除算法(Mark-Sweep)首先标记出所有需要回收的对象,标记完成后统一回收所有被标记对象。主要不足之处: 效率问题标记和清除两个过程的效率都不高。正如上篇笔记所记,GC会从 GC Roots 开始遍历标记,然后从 Java 堆从头到尾遍历清除垃圾。 空间问题会产生大量不连续的空间,所以无法分配大对象,最终因为空间问题不得不提前触发 GC 动作。 复制算法(Copying) 复制算法是为了解决标记-清除算法的效率问题。 将可用内存的容量分为大小相等的两块,每次只使用其中的一块,当这一块内存使用完了,就把存活着的对象复制到另外一块上面,然后再把已使用过的内存空间清理掉。但是,这种做法的代价是将内存缩小为原来的一半。 关于该算法的商业应用: 目的:利用这种算法来回收新生代。 方案:将内存分为 Eden 与 Survivor(有2块) 空间,比例一般为 8:1:1。每次回收将存活对象复制到一块 Survivor 空间,其他空间全部回收。 空间保证(分配担保机制):因为无法保证回收存活对象的内存大小,只能依赖其他内存(指老年代)进行分配担保。 代价:每次回收只有一块 Survivor 空间(10%内存)被浪费。 标记-整理算法(Mark-Compact)在标记后不是对未标记的内存区域进行清理,二是让所有的存活对象都向一端移动,然后清理掉边界外的内存。该方法主要用于老年代。 分代收集算法将Java堆分为几个空间: 新生代 特点:大批对象死去,只有少量存活。 算法:复制算法。 老年代 特点:对象存活率高,没有额外空间进行分配担保。 算法:标记-清除算法/标记-整理算法。 参考资料 《深入了解Java虚拟机》(周志明 著)","categories":[{"name":"JVM学习笔记","slug":"JVM学习笔记","permalink":"https://zggdczfr.cn/categories/JVM学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"}]},{"title":"JVM学习笔记(二)垃圾收集器","slug":"JVM学习笔记-二-垃圾收集器","date":"2017-03-21T11:09:22.000Z","updated":"2017-03-22T13:41:10.736Z","comments":true,"path":"2017/03/21/JVM学习笔记-二-垃圾收集器/","link":"","permalink":"https://zggdczfr.cn/2017/03/21/JVM学习笔记-二-垃圾收集器/","excerpt":"有关于JVM的垃圾回收机制,以及GC的几个区域,和其他一些零碎的记录~","text":"有关于JVM的垃圾回收机制,以及GC的几个区域,和其他一些零碎的记录~ 垃圾收集器 主要通过阅读《深入了解Java虚拟机》(周志明 著)和网络资源汇集而成,为本人学习JVM的笔记。同时,本文理论基于JDK 1.7版本,暂不考虑 1.8和1.9 的新特性,但可能初略提到。 一、GC概念垃圾收集(GC,Garbage Collection),就是在动态分配内存后对内存进行自动回收。 哪些内存需要回收? 已死对象所占的内存需要回收。 什么时候回收? 当内存不够用时执行垃圾回收,主要分为 Minor GC(新生代垃圾回收) 和 Major GC(又称 Full GC,老年代垃圾回收)。 堆(head)可以分为 Eden Space(新手村)、Survivor Space(幸存者区) 和 Tenured Gen(养老区)。对象会被优先分配到 Eden 区,大对象会直接分配到 Tenured Gen。 当 Eden 区满了的话发生 Minor GC,有引用的对象将被移到 Survivor 区。Survivor 区定期(可以自定义)进行GC;经历过一定次数GC仍然幸存的对象,将被送入到 Tenured Gen。当Tenured Gen 满了会发生 Major GC,或者受 HandlePromotionFailure 参数控制强制 Major GC。 如何回收?新生代由于对象产生比较多并且大都是朝生夕灭,所以一般使用复制算法;而老年代的生命力很强,所以建议使用标记-助理算法。 二、判断对象是否可以回收 程序计数器、虚拟机栈和本地方法栈,对于线程而言是私有的。当线程结束时,它们会被自动回收,所以不需要过多考虑回收问题。 对于Java程序而言,需要回收内存的地方主要就是堆(大部分对象的存放位置),回收对堆内存的分配。判断方法: 1. 引用计数法给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器 值减1;如何时刻计数器值为0的对象就是不能再被使用的。但是,这种方法难以解决对象之间相互循环引用的问题(主流的Java虚拟机没有引用这种计数方法来管理内存)。 2. 可达性分析算法(根搜索算法)通过一系列名为”GC Roots“对象作为起始点,从这些节点开始往下搜索,搜索过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明该对象是不可用的,会被判定为可回收对象。GC Roots 对象(1) 虚拟机栈(栈帧中的本地变量表)中引用的对象。(2) 方法区中类静态属性引用的对象。(3) 方法区中常量引用的对象。(4) 本地方法栈中引用的对象(Native 对象)。 3. 四种引用以上谈到的两种算法其实都与”引用”有关。(1)强引用(Strong Reference)在代码中普遍存在。只要强引用还存在,垃圾收集器永远不会回收被引用的对象。当内存空间不足时,Java虚拟机宁愿抛出OOM,使程序异常终止。(2)软引用(Soft Reference)在内存空间不足的情况下,虚拟机才会回收这种对象。软引用只要通过SoftReference 类来实现,可以作为内存敏感的高速缓存。(3)弱引用(Weak Reference)GC后都会回收的一类对象,可以通过WeakReference 类来实现。(4)虚引用(Phantom Reference)可以通过PhantomReference 类来实现,为一个对象设置虚引用的唯一目的就是能在这个对象收集器回收时收到一个系统通知。 三、生存还是死亡一个对象死亡的经历: 如果对象在进行可行性分析后发现没有与 GC Roots 相连接的引用链,则将其第一次标记并进行一次筛选。 筛选条件:该对象有没有必要执行 finalize() 方法 没有必要执行的情况:对象没有覆盖 finalize() 方法;finalize() 方法已经被虚拟机调用过了。 若有必要执行 finalize() 方法,将对象放到 F-Queue 队列中。 虚拟机自动建立 Finalizer 线程(低优先级)去执行该方法(PS:若对象在该方法中执行缓慢甚至死循环,会导致严重后果,甚至导致内存回收系统崩溃)。 finalize() 也是对象逃脱死亡的最后一次机会: GC 会对队列中对象进行第二次标记。 若对象与引用链上任何一个对象建立关联,即可脱离被回收的命运。 第二次标记后,将对象移出队列,并最终被系统回收。 任何一个对象的 finalize() 方法只会被系统调用一次! finalize() 方法的代价高昂,不确定性大,无法保证各个对象的调用顺序。完全可以使用 try-finally 等方法来实现它的功能。但还是要了解一下Java的对于判断对象存亡的执行机制。 其他 当GC与非GC时间耗时超过了 GCTimeRatio 的限制时,会触发 OOM。 GC调优: 用 NewRatio 来控制新生代和老年代的比例。 用 MaxTenuringThreshold 来控制进入老年代前的生存次数。 使老年代存储空间延迟达到 Major GC。 参考资料 《深入了解Java虚拟机》(周志明 著)","categories":[{"name":"JVM学习笔记","slug":"JVM学习笔记","permalink":"https://zggdczfr.cn/categories/JVM学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"}]},{"title":"JVM学习笔记(一) Java内存区域","slug":"JVM学习笔记-一-Java内存区域","date":"2017-03-19T16:10:46.000Z","updated":"2017-03-19T16:16:24.426Z","comments":true,"path":"2017/03/20/JVM学习笔记-一-Java内存区域/","link":"","permalink":"https://zggdczfr.cn/2017/03/20/JVM学习笔记-一-Java内存区域/","excerpt":"最近花了好几天的时间大概地浏览了一下《深入了解Java虚拟机》,果然基本上都忘得差不多了。接下来就必须抽出时间好好地读一下这本书了,多记笔记、多记笔记、多记笔记!!!","text":"最近花了好几天的时间大概地浏览了一下《深入了解Java虚拟机》,果然基本上都忘得差不多了。接下来就必须抽出时间好好地读一下这本书了,多记笔记、多记笔记、多记笔记!!! Java 内存区域总概java虚拟机在执行java程序的过程中,会把它管理的内存划分为几个不同的数据区域。每当运行一个java程序时,就会启动一个虚拟机。具体的区域如图所示: 同时,方法区 与 堆 是由所有线程共享的数据区;而 虚拟机栈、本地方法栈、程序计数器 则是被线程隔离的区域。 一、程序计数器 什么是程序计数器?概念:就是当前线程所执行的字节码的行号指示器。 JVM的概念模型中,字节码解释器通过改变这个计数器的值来选取下一条字节码指令。 JVM的多线程其实就是通过线程轮流切换并分配处理器执行时间的方式来实现的(在任何一个确定的时刻内,一个处理器都只会执行一条线程中的指令)。为了线程切换后能够恢复到正确的执行位置,每条线程都需要有独立的程序计数器,各线程计数器互不影响,独立存储。所以,程序计数器是线程私有的内存区域 如果线程执行一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Native方法,则计数器的值为空。 Java虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域。 二、Java虚拟机栈 线程私有,生命周期与线程相同。 虚拟机描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。(PS:我觉得可以将它看作是一个方法的快照,记录着方法的参数之类的信息,其实就是方法运行时的数据结构基础) 局部变量表,存放了各种基本数据类型、对象引用,和返回后所指向的字节码的地址。(PS:这个就是我们常说的“栈内存 Stack”) 在Java虚拟机规范中,对于该区域规定了两种异常状态: StackOverflowEError : 线程请求的深度大于虚拟机所允许的深度; OutOfMemoryError : 动态扩展时无法申请到足够的内存。 三、本地方法栈 本地方法栈为虚拟机使用到 Native 方法服务。 同 Java虚拟机栈 一样,会抛出 StackOverflowEError 和 OutOfMemoryError 异常。 四、Java堆(线程共享) C语言是使用 malloc 从堆中来分配空间的。同样的,Java堆是用来存放对象实例的。Java规范中的描述:所有的对象实例以及数组都要在堆上分配;但随着技术的发展,这种说法也不是那么“绝对”了。 唯一目的就是存放实例对象,几乎所有的对象实例都在这里分配内存。 Java堆是垃圾收集器管理的主要区域。 可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,同时也是可扩展的。 OutOfMemoryError : 如果在堆中没有内存完成实例分配,并且堆也无法再扩展。 五、方法区(线程共享) 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 方法区对于垃圾回收的效果比较难以令人满意,尤其是对于类型的卸载条件相当坎坷,但对于该区域进行垃圾回收是必要的。 OutOfMemoryError : 如果方法区无法满足内存分配需求就会抛出这个异常。 常量池 常量池是方法区的一部分。这里存放着编译期生成的各种字面量和符号引用(其实就是八大基本类型的包装类型和String类型数据)。 运行时常量池一个重要的特征:具备动态性,解释就是Java语言并不要求常量一定只有编译期才产生,比如String类的intern()方法。 String.intern()String类的intern()方法是一个Native方法,底层调用C++的 StringTable::intern方法实现。1234567 public static void main(String[] args) { //String str = \"FunriLy\"; String str1 = new StringBuilder(\"Funri\").append(\"Ly\").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder(\"java\").toString(); System.out.println(str2.intern() == str2); } 运行上面的代码,会得到一true一false;若去掉注释,则会得到两个false。 * 调用intern()后,JVM就会在当前类的常量池中查找是否存在与str等值的String,若存在则直接返回常量池中相应Strnig的引用;若不存在,则会在常量池中创建一个等值的String,然后返回这个String在常量池中的引用。 * 在 JDK 1.6 版本,**常量池被保存在方法区(PermGen)中**,而String类对象存在于堆区中,这就意味着多次intern()操作会使内存中存在许多重复的字符串,会造成性能损失。同时,在此区域其大小会受到限制。 * 在 JDK 1.7 版本,开始了"永久代"的移除(也就是前面提到过GC的要求):符号引用转移到了本地方法栈;字面量转移到了堆;类的静态变量转移到了堆。 * 在iJDK 1.8 版本,去掉了PermGen内存,所以永久代的参数 -XX:PermSize 和 -XX:MaxPermSize 也被移除了,转而出现了一个元空间(Metaspace)。【关于这一点,可以看一下这篇博文:http://blog.csdn.net/zhyhang/article/details/17246223 】 六、对象的创建 当虚拟机遇到一条 new 指令时,首先去常量池中检查是否能定位到一个类的符号引用,并检查类是否已经被加载、解析和初始化过。如果没有,就执行相应的加载操作。 接下来就是在堆中分配空间了。有两种方案: 第一种(指针碰撞法)假设堆中内存绝对规整,那么只要在用过的内存和没用过的内存间放置一个指针即可,每次分配空间的时候只要把指针向空闲空间移动相应距离即可。 第二种(空闲列表)假设内存空间并不规整,通过维护一个列表来记录堆内存的使用情况(PS:操作系统对于内存的管理就是这种模式)。 但是我们也要考虑在并发情况下的线程安全性问题。比如,正在给对象A分配空间,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存,导致对象AB占用了相同的一块空间。 第一种,对分配内存空间的动作进行同步处理。 第二种,每个线程在堆中预先分配一小块内存,称为本地线程分配缓存(TLAB),每个线程只在自己的 TLAB 中分配内存。 最后,对象被成功分配内存空间,虚拟机会对对象进行必要的设置,对象的类,对象的哈希码等信息都存放在对象的对象头中,所以分配的内存大小绝不止属性的总和。 七、对象的内存布局 对于大部分的虚拟机,对象在堆中的存储布局可以分为3块区域: 对象头,包括两部分信息,第一部分用于存储对象自身运行时数据,例如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳,官方称为”Mark Word”。另外一部分是类型指针,即对象指向它的类元数据指针(即指向方法区类数据的指针)。 实例数据,存放着对象真正存储的有效信息,包括了父类继承和子类定义的信息。 对齐填充,占位符作用。 八、对象的访问定位 引用存放在虚拟机栈中,数据类型为reference,对象实例存放在堆中。 Java程序就是通过栈上的reference数据来操作堆上的具体对象。目前,主流的访问方式有使用句柄和直接指针两种。 使用句柄来访问对象使用句柄的话,将会在Java堆中划分一块区域作为句柄池,引用中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。 优势:在对象被移动时(比如垃圾回收)只会改变句柄中的实例数据指针,而引用本身不需要修改。 通过指针来访问对象使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而引用中存储的直接就是对象地址。 优势:访问速度快,节省了一次指针定位(访问对象是非常频繁的操作)的时间开销。 参考资料 《收入了解Java虚拟机》(周志明 著) (String类intern()方法)http://www.jianshu.com/p/95f516cb75ef","categories":[{"name":"JVM学习笔记","slug":"JVM学习笔记","permalink":"https://zggdczfr.cn/categories/JVM学习笔记/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"}]},{"title":"(转)Spring Cloud构建微服务架构:服务网关","slug":"(转)Spring-Cloud构建微服务架构-服务网关","date":"2017-03-18T01:59:12.000Z","updated":"2017-03-18T02:07:08.224Z","comments":true,"path":"2017/03/18/(转)Spring-Cloud构建微服务架构-服务网关/","link":"","permalink":"https://zggdczfr.cn/2017/03/18/(转)Spring-Cloud构建微服务架构-服务网关/","excerpt":"转载于 http://blog.didispace.com/springcloud5/#comments","text":"转载于 http://blog.didispace.com/springcloud5/#comments Spring Cloud构建微服务架构:服务网关通过之前几篇Spring Cloud中几个核心组件的介绍,我们已经可以构建一个简略的(不够完善)微服务架构了。比如下图所示: 我们使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过Ribbon或Feign实现服务的消费以及均衡负载;通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。 在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是一个对外的服务,通过均衡负载公开至服务调用方。本文我们把焦点聚集在对外服务这块,这样的实现是否合理,或者是否有更好的实现方式呢? 先来说说这样架构需要做的一些事儿以及存在的不足: 首先,破坏了服务无状态特点。为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。 其次,无法直接复用既有接口。当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。 面对类似上面的问题,我们要如何解决呢?下面进入本文的正题:服务网关! 为了解决上面这些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器,它就是本文将来介绍的:服务网关。 服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。 下面我们通过实例例子来使用一下Zuul来作为服务的路有功能。 准备工作在使用Zuul之前,我们先构建一个服务注册中心、以及两个简单的服务,比如:我构建了一个service-A,一个service-B。然后启动eureka-server和这两个服务。通过访问eureka-server,我们可以看到service-A和service-B已经注册到了服务中心。 如果您还不熟悉如何构建服务中心和注册服务,请先阅读Spring Cloud构建微服务架构(一)服务注册与发现。 如果您不想自己动手准备,可以从这里获取示例代码:http://git.oschina.net/didispace/SpringBoot-Learning 开始使用Zuul 引入依赖spring-cloud-starter-zuul、spring-cloud-starter-eureka,如果不是通过指定serviceId的方式,eureka依赖不需要,但是为了对服务集群细节的透明性,还是用serviceId来避免直接引用url的方式吧。 12345678<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId></dependency> 应用主类使用@EnableZuulProxy注解开启Zuul1234567@EnableZuulProxy@SpringCloudApplicationpublic class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }} 这里用了@SpringCloudApplication注解,之前没有提过,通过源码我们看到,它整合了@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker,主要目的还是简化配置。这几个注解的具体作用这里就不做详细介绍了,之前的文章已经都介绍过。 application.properties中配置Zuul应用的基础信息,如:应用名、服务端口等。12spring.application.name=api-gatewayserver.port=5555 Zuul配置完成上面的工作后,Zuul已经可以运行了,但是如何让它为我们的微服务集群服务,还需要我们另行配置,下面详细的介绍一些常用配置内容。 服务路由通过服务路由的功能,我们在对外提供服务的时候,只需要通过暴露Zuul中配置的调用地址就可以让调用方统一的来访问我们的服务,而不需要了解具体提供服务的主机信息了。 在Zuul中提供了两种映射方式: 通过url直接映射,我们可以如下配置:123# routes to urlzuul.routes.api-a-url.path=/api-a-url/**zuul.routes.api-a-url.url=http://localhost:2222/ 该配置,定义了,所有到Zuul的中规则为:/api-a-url/**的访问都映射到http://localhost:2222/上,也就是说当我们访问http://localhost:5555/api-a-url/add?a=1&b=2的时候,Zuul会将该请求路由到:http://localhost:2222/add?a=1&b=2上。 其中,配置属性zuul.routes.api-a-url.path中的api-a-url部分为路由的名字,可以任意定义,但是一组映射关系的path和url要相同,下面讲serviceId时候也是如此。 通过url映射的方式对于Zuul来说,并不是特别友好,Zuul需要知道我们所有为服务的地址,才能完成所有的映射配置。而实际上,我们在实现微服务架构时,服务名与服务实例地址的关系在eureka server中已经存在了,所以只需要将Zuul注册到eureka server上去发现其他服务,我们就可以实现对serviceId的映射。例如,我们可以如下配置:12345zuul.routes.api-a.path=/api-a/**zuul.routes.api-a.serviceId=service-Azuul.routes.api-b.path=/api-b/**zuul.routes.api-b.serviceId=service-Beureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ 针对我们在准备工作中实现的两个微服务service-A和service-B,定义了两个路由api-a和api-b来分别映射。另外为了让Zuul能发现service-A和service-B,也加入了eureka的配置。 接下来,我们将eureka-server、service-A、service-B以及这里用Zuul实现的服务网关启动起来,在eureka-server的控制页面中,我们可以看到分别注册了service-A、service-B以及api-gateway alt 尝试通过服务网关来访问service-A和service-B,根据配置的映射关系,分别访问下面的url http://localhost:5555/api-a/add?a=1&b=2:通过serviceId映射访问service-A中的add服务 http://localhost:5555/api-b/add?a=1&b=2:通过serviceId映射访问service-B中的add服务 http://localhost:5555/api-a-url/add?a=1&b=2:通过url映射访问service-A中的add服务 推荐使用serviceId的映射方式,除了对Zuul维护上更加友好之外,serviceId映射方式还支持了断路器,对于服务故障的情况下,可以有效的防止故障蔓延到服务网关上而影响整个系统的对外服务 服务过滤在完成了服务路由之后,我们对外开放服务还需要一些安全措施来保护客户端只能访问它应该访问到的资源。所以我们需要利用Zuul的过滤器来实现我们对外服务的安全控制。 在服务网关中定义过滤器只需要继承ZuulFilter抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。 比如下面的例子,定义了一个Zuul过滤器,实现了在请求被路由之前检查请求中是否有accessToken参数,若有就进行路由,若没有就拒绝访问,返回401 Unauthorized错误。123456789101112131415161718192021222324252627282930public class AccessFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(AccessFilter.class); @Override public String filterType() { return \"pre\"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format(\"%s request to %s\", request.getMethod(), request.getRequestURL().toString())); Object accessToken = request.getParameter(\"accessToken\"); if(accessToken == null) { log.warn(\"access token is empty\"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); return null; } log.info(\"access token ok\"); return null; }} 自定义过滤器的实现,需要继承ZuulFilter,需要重写实现下面四个方法: filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下: pre:可以在请求被路由之前调用 routing:在路由请求时候被调用 post:在routing和error过滤器之后被调用 error:处理请求时发生错误时被调用 filterOrder:通过int值来定义过滤器的执行顺序 shouldFilter:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效。 run:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)对返回body内容进行编辑等。 在实现了自定义过滤器之后,还需要实例化该过滤器才能生效,我们只需要在应用主类中增加如下内容: 1234567891011@EnableZuulProxy@SpringCloudApplicationpublic class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } @Bean public AccessFilter accessFilter() { return new AccessFilter(); }} 启动该服务网关后,访问: http://localhost:5555/api-a/add?a=1&b=2:返回401错误 http://localhost:5555/api-a/add?a=1&b=2&accessToken=token:正确路由到server-A,并返回计算内容 对于其他一些过滤类型,这里就不一一展开了,根据之前对filterType生命周期介绍,可以参考下图去理解,并根据自己的需要在不同的生命周期中去实现不同类型的过滤器。 alt 最后,总结一下为什么服务网关是微服务架构的重要部分,是我们必须要去做的原因: 不仅仅实现了路由功能来屏蔽诸多服务细节,更实现了服务级别、均衡负载的路由。 实现了接口权限校验与微服务业务逻辑的解耦。通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。 实现了断路器,不会因为具体微服务的故障而导致服务网关的阻塞,依然可以对外服务。 本文完整示例可参考:Chapter9-1-5 【转载请注明出处】:http://blog.didispace.com/springcloud5/ 本文由 程序猿DD-翟永超 创作,采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。","categories":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/categories/spring-cloud/"}],"tags":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/tags/spring-cloud/"}]},{"title":"spring session 单用户多账号登录","slug":"spring-session-单用户多账号登录","date":"2017-03-05T09:09:33.000Z","updated":"2017-03-05T09:15:28.896Z","comments":true,"path":"2017/03/05/spring-session-单用户多账号登录/","link":"","permalink":"https://zggdczfr.cn/2017/03/05/spring-session-单用户多账号登录/","excerpt":"Spring Session Multiple Browser Sessions - Spring Session supports managing multiple users’ sessions in a single browser instance (i.e. multiple authenticated accounts similar to Google).","text":"Spring Session Multiple Browser Sessions - Spring Session supports managing multiple users’ sessions in a single browser instance (i.e. multiple authenticated accounts similar to Google). spring session 单用户多账号登录前言昨天在查看 spring session 官网时发现一段有趣的话(http://projects.spring.io/spring-session/): Multiple Browser Sessions - Spring Session supports managing multiple users’ sessions in a single browser instance (i.e. multiple authenticated accounts similar to Google). 这段话大意就是说 spring session 支持一个用户在浏览器上登录多个账户的功能。一般情况下,我们是通过开启多个浏览器同时登录某个系统,实现自己多个账户的同时操作。其实,Chrome 浏览器已经实现了多个 Google 账户在浏览器上的操作。 个人参考案例个人博客 : https://zggdczfr.cn/个人参考案例(如果认可的话请给个star) : https://github.com/FunriLy/springboot-study/tree/master/%E6%A1%88%E4%BE%8B10 正式实现这里我用的是 spring boot 学习(十四)SpringBoot+Redis+SpringSession缓存之实战,这个前天的案例。通过仔细阅读官方的操作文档 Spring Session - Multiple Sessions,我稍微理解了该如何来使用多账户登录。一般情况下,我们登录访问系统时,Spring Session 会默认一个请求参数_s为 0.通过修改这个参数,来启用 SpringSession 为用户分配新的 SessionId。 实现机制通过稍微阅读 SpringSession 源码 与几位大神的博客,我大概知道了其中的机制。 spring session通过增加session alias概念来实现多用户session,每一个用户都映射成一个session alias。当有多个session时,spring会生成“alias1 sessionid1 alias2 sessid2…….”这样的cookie值结构。 每当spring session提交时如果有新session生成,会触发onNewSession动作生成新的session cookie。 onNewSession 是 Spring Session 源码之中一个重要的方法 注意:如果只有一个session id,则和普通session cookie一样处理,cookie值就是session id。如果存在多个session id,就会形成上面提到的那种 alias 结构的 session cookie。 坑问题等我信心满满等按照官网操作方法去搞时,却失败了,爆了个异常1java.lang.IllegalArgumentException: An invalid character [32] was present in the Cookie value 报得我一脸蒙蔽……google之后,原来是 Tomcat 在 8.5.X 版本后不使用 Servlet 3.1 的标准,改用RFC 6265 来实现。这样子的话,会导致报文输入时多了个空格(字符32)。 解决方案重写 Tomcat 的解析方法,代码来源于Github,并经过修改。1234567891011121314151617181920@Configurationpublic class CookieConfig { @Bean public EmbeddedServletContainerCustomizer customizer(){ return new EmbeddedServletContainerCustomizer() { @Override public void customize(ConfigurableEmbeddedServletContainer container) { TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container; tomcat.addContextCustomizers( new TomcatContextCustomizer() { @Override public void customize(Context context) { context.setCookieProcessor(new LegacyCookieProcessor()); } }); } }; }} 将方法重写后注入 bean 中,交给容器去管理。 测试启动工程,在同一个浏览器中分别访问http://localhost:8080/session?_s=0http://localhost:8080/session?_s=1http://localhost:8080/session?_s=2 我们可以看到结果(三个sessionId都不一样):12345{"SessionId":"a1d59666-10a4-4dc3-a1b0-1166cda8aab2","ServerPort":"服务端口号为 8080"}{"SessionId":"21177055-70e2-426c-9292-40ccf825078b","ServerPort":"服务端口号为 8080"}{"SessionId":"1eade000-f2e1-4bf9-9aef-1510d39441e4","ServerPort":"服务端口号为 8080"} 参考资料 https://github.com/spring-projects/spring-session http://docs.spring.io/spring-session/docs/current/reference/html5/guides/users.html https://github.com/spring-projects/spring-boot/issues/6827 (配置代码主要来源)https://github.com/jomolina/session-couchbase-spring-boot-starter/commit/e7b0487b92b65408c485a7d25b89a64c9fb4fc89","categories":[{"name":"spring session","slug":"spring-session","permalink":"https://zggdczfr.cn/categories/spring-session/"}],"tags":[{"name":"spring session","slug":"spring-session","permalink":"https://zggdczfr.cn/tags/spring-session/"}]},{"title":"让个人域名下GithubPage完美支持https","slug":"让个人域名下GithubPage完美支持https","date":"2017-02-25T12:04:03.000Z","updated":"2017-03-05T04:22:11.117Z","comments":true,"path":"2017/02/25/让个人域名下GithubPage完美支持https/","link":"","permalink":"https://zggdczfr.cn/2017/02/25/让个人域名下GithubPage完美支持https/","excerpt":"欢迎访问完美HTTPS支持的GithubPage个人博客 : https://zggdczfr.cn/","text":"欢迎访问完美HTTPS支持的GithubPage个人博客 : https://zggdczfr.cn/ 让个人域名下GithubPage完美支持https前言最近笔记本挂了送去维修,耽误了我的学习计划,就先把月初的一点小技巧发出来充充数……话说,没了笔记本的日子,看书反而更加认真了~遵从我2016年10月时,定下的目标:于2017年开始要坚持写博客。于是,我在GithubPage上搞了个静态博客网站 https://zggdczfr.cn/ 。作为一个有个性并略有强迫症的程序猿,肯定要给自己的博客弄个个人域名,再配上一把小小的绿锁头。腾讯云专门为大学生推出了服务器和个人域名的优惠政策,鉴于这个域名是去年申请的,虽然不是很有个人特色也只能勉强用着先。 准备 仓库:Github Hexo 博客主题:icarus 评论系统:多说 SSL证书:CloudFlare 个人域名(腾讯云):https://zggdczfr.cn/ 为什么要使用 HTTPS 协议? 虽然SSL并不是无懈可击的,但是我们应该尽可能提高窃听成本 加密通讯不应心存侥幸,所有连接都应被加密 福利: 使用了HTTPS之后,如果网站的访客是从其他已经使用了HTTPS的网站上跳转过来,你就能在Google Analytics中获取更完整的来源信息。 不过关于最后一点,我不得不吐槽一下”墙”,它导致 Google Analytics 的信息延迟长达一天以上,最后我不得不退而选择 Baidu Analytics。 在Github上搭建 Hexo 主题博客关于这个我就不罗嗦了,网上一大堆教程,分享一篇非常详细的博客(里面包括了GithubPage应用自定义域名) : https://xuanwo.org/2015/03/26/hexo-intor/不过,绑定后我们只能通过http://域名来访问。如果访问https://XXX.github.io/(即原来的域名)将会被重定向(302)到我们的自定义域名。若直接访问https://域名,浏览器会报SSL_DOMAIN_NOT_MATCHED警告。 CloudFlare 给自己的域名加个 s首先,GitHub Pages不支持上传SSL证书。CloudFlare 是一家CDN提供商,它提供了免费的https服务(但不是应用SSL证书)。实现模式就是,用户到CDN服务器的连接为https,而CDN服务器到GithubPage服务器的连接为http,就是在CDN服务器那里加上反向代理。 注册并登录CloudFlare,并将自己域名下的 name server 修改为 CloudFlare 的 name server。 在 CloudFlare 的 DNS 设置域名匹配到自己的GithubPage(启用动态DNS加速)。 在 CloudFlare 的 Crypto 设置 SSL 为 Flexible(等待一定时间实现建立连接后,就可以通过https来访问自己的 GithubPage )。 在 CloudFlare 的 Page Rules 中设置路由规则。一般情况下,利用Always use https设置两条规则,规则链接分别为http://域名/*与http://域名/(开启https强制跳转)。 关于评论系统如果使用国外的评论系统disqus,这个的确完美支持 https。但,所限于用户限制,也只能舍弃。对于国内的评论系统,主要就是友言(有JS文件支持不了HTTPS引用),多说(这个默认属性并不支持HTTPS但可以进行改造)。 对”多说”进行了解使用”多说”有一个缺点,他们的服务器不知道为何会偶尔性挂掉。 多说的评论框虽然提供了https链接,但是其中的一些头像和表情还是http的。通过F12,我们可以发现多说的embed.js请求头像和表情的时候用的是http协。但是,这两个链接本身也支持https协议。先给个embed.js的官方下载链接 : https://static.duoshuo.com/embed.js 下载后的JS文件是经过压缩的,所以我们需要用 Sublime Text 来进行格式话,以方便我们阅读。 修改头像链接经过漫长的Debug,我终于找到了头像链接的相应位置。搜索avatar_url找到头像链接(展示其中部分代码),:1234 var Z = { userUrl: function(e) { return e.url }, avatarUrl: function(e) { return e.avatar_url || rt.data.default_avatar_url }, loginUrl: function(e, t) { 简单的字符串替换为”https”,将其修改为:123456 var Z = { userUrl: function(e) { return e.url }, avatarUrl: function(e) { var s = e.avatar_url || rt.data.default_avatar_url s=s.replace(/http:/g,'https:'); return s }, loginUrl: function(e, t) { 修改表情链接经过Debug,发现传送的属性是meaasge,它的上一个属性是s。最终查了好久之后终于发现了它的位置(这个没有上一个明显),搜索s = e.post就可以找到它的位置(注意等号旁边的空格)。1234 var t = \"\", s = e.post, i = e.options, r = s.author; 简单的字符串替换为”https”,将其修改为:12345 var t = \"\", s = e.post, i = e.options, r = s.author; s.message = s.message.replace(/http:/g,\"https:\"); 修改表情按钮链接这个最简单了,Debug发现按钮链接为”http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/“ ,在JS文件中将其修改为 https 即可。 替换embed.js的路径以Hexo主题icarus为例,在themes\\icarus\\layout\\comment文件夹下找到多说的调用ejs文件。我将上面修改过的JS文件放到了主题的资源JS文件夹,然后在相应的ejs文件中修改调用路径即可。123 ds.async = true; ds.src = '/js/embed.js'; ds.charset = 'UTF-8'; 完美应用利用hexo命令hexo g && hexo d,重新将静态资源pull到Github上。欢迎大家去访问一下我的个人博客 https://www.zggdczfr.cn/ 。 关于图床的问题https引用的图片一直是略微麻烦的事情。 第一种,是将图片放到Github上。但是,使用相对路径引用的话,会出现跳转页面时一些图片路径会错误;使用绝对路径引用的话,会出现重定向的问题,浏览器报302异常,使浏览器上的绿色小锁头不见了(不能容忍……)。 第二种,是将图片放到七牛云上面,不过注册用户每个月有流量限制,我担心若访问人数太多(虽然不大可能)会使图片失效。 第三种,从知乎上找到了支持 https 的图床,虽然不知道能使用多久(我现在也就是使用这种)。给个链接 : https://www.tuchuang001.com/ 第四种,在自己的私人服务器上搭一个(因为自己的腾讯云服务器要经常做一些测试,就不选择放到上面了)。 GithubPage 个人博客 https://www.zggdczfr.cn/","categories":[{"name":"其他奇奇怪怪的","slug":"其他奇奇怪怪的","permalink":"https://zggdczfr.cn/categories/其他奇奇怪怪的/"}],"tags":[{"name":"https","slug":"https","permalink":"https://zggdczfr.cn/tags/https/"}]},{"title":"Spring Cloud Config(续)","slug":"Spring-Cloud-Config-续","date":"2017-02-21T12:17:49.000Z","updated":"2017-02-21T12:21:54.528Z","comments":true,"path":"2017/02/21/Spring-Cloud-Config-续/","link":"","permalink":"https://zggdczfr.cn/2017/02/21/Spring-Cloud-Config-续/","excerpt":"","text":"Spring Cloud Config(续)个人参考项目个人博客 : https://zggdczfr.cn/个人参考项目 : (整合到上一个案例中)https://github.com/FunriLy/springcloud-study/tree/master/%E6%A1%88%E4%BE%8B5 为 Config Client 配置配置刷新场景介绍在上一个案例,我们成功配置了 Config Server 与 Config Client。依次启动两个项目。 访问API接口 http://localhost:1111/config 来获取配置信息: 1The Config Word Is : Hello World ! 利用 Git 提交我们修改后配置文件。 重新访问API接口 http://localhost:1111/config 来获取配置信息: 1The Config Word Is : Hello World ! 由此可见,配置资源的更新不能即时通知到 Server Client。 实现配置文件更新 引入依赖 12345<!-- actuator 监控 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 在 Controller层加入注解@RefreshScope如果 @Controller 是加在单独的类中声明的(不是在Application启动类上声明)。那么@RefreshScope要加在声明@Controller声明的类上,否则refresh之后Conroller拿不到最新的值,会默认调用缓存。 通过POST请求发送到 http://localhost:1111/refresh ,我们可以看到以下内容: 123[ \"configword\"] 重新访问API接口 http://localhost:1111/config 来获取配置信息: 1The Config Word Is : NewConfig !","categories":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/categories/spring-cloud/"}],"tags":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/tags/spring-cloud/"}]},{"title":"Spring Cloud Config","slug":"Spring-Cloud-Config","date":"2017-02-20T12:45:14.000Z","updated":"2017-02-20T12:49:33.984Z","comments":true,"path":"2017/02/20/Spring-Cloud-Config/","link":"","permalink":"https://zggdczfr.cn/2017/02/20/Spring-Cloud-Config/","excerpt":"Spring Cloud Config配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。","text":"Spring Cloud Config配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。 Spring Cloud Config 参考个人项目参考个人项目 : (希望大家能给个star~)https://github.com/FunriLy/springcloud-study/tree/master/%E6%A1%88%E4%BE%8B5 什么是 Spring Cloud Config?配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。场景介绍:在一个 Application 中,很经常需要连接资源和其它应用,经常有很多需要外部设置的信息去调整Application行为。我们在实际开发应用中的会经常见到的xml、properties、yaml等就是配置信息,但这种做法有一定的缺陷:每次更新需要重新打包和重启。 创建 Spring Cloud Config Server(Git 存储) 这里我用了我原本的”服务注册中心”(Eureka Server),将其改造为”配置中心”(Config Server)。 引入依赖 12345 <!-- Config Server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> 在启动主类添加@EnableConfigServer注解,开启 Config Server。 12345678@SpringBootApplication@EnableEurekaServer@EnableConfigServerpublic class MySpringCloudApplication { public static void main(String[] args) { SpringApplication.run(MySpringCloudApplication.class, args); }} 在Github上创建一个项目,并在其中添加配置文件 config-client.properties,在里面添加一个属性config=hello world !。 在application.properties中配置服务信息以及git信息(这里不包括了 Eureka Server 的配置,自行补充) 12345server.port=8761spring.cloud.config.server.git.uri=https://github.com/FunriLy/springcloud-study/spring.cloud.config.server.git.searchPaths=config-repospring.cloud.config.server.git.username=Usernamespring.cloud.config.server.git.password=Password 启动工程 Config Server。访问 http://localhost:8761/config-client/default/ (关于这个URL请参考附录),可以看到以下配置信息: 12345678910111213141516{ \"name\": \"config-client\", \"profiles\": [ \"default\" ], \"label\": null, \"version\": \"af7ce2a15dcdea9dab42e6b44d37e401072382d8\", \"propertySources\": [ { \"name\": \"https://github.com/FunriLy/springcloud-study/config-repo/config-client.properties\", \"source\": { \"configword\": \"hello world !\" } } ]} 创建一个Spring Cloud Config Client 这里我用到了原来的”服务提供者”(Eureka Client),将其改造为 Config Client。 在resource下创建bootstrap.properties,并设置信息,具体如下: 1234spring.application.name=config-clientspring.cloud.config.profile=defaultspring.cloud.config.label=masterspring.cloud.config.uri=http://localhost:8761/ 注意这里是bootstrap.properties而不是appliction.properties。因为bootstrap.properties会在应用启动之前读取,而spring.cloud.config.uri会影响应用启动 创建一个Controller来进行测试。 1234567891011@RestControllerpublic class ConfigController { @Value(\"${configword}\") String configword; @RequestMapping(\"/config\") public String printfConfig(){ return \"The Config Word Is : \"+configword; }} 启动 Config Client,访问 http://localhost:1111/config 。就能看到: 1The Config Word Is : hello world ! 附录来源于 Spring Cloud Config 中文文档。 URL与配置文件的映射关系 /{application}/{profile}[/{label}] /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties application是SpringApplication的spring.config.name,(一般来说’application’是一个常规的Spring Boot应用),profile是一个active的profile(或者逗号分隔的属性列表),label是一个可选的git标签(默认为”master”)。比如,我的文件名是”config-client”,一般在Github上都是default环境,默认为master分支。所以就是/config-client/default/master Config Server 配置文件 spring.cloud.config.server.git.uri:配置git仓库位置 spring.cloud.config.server.git.searchPaths:配置仓库路径下的相对搜索位置,可以配置多个 spring.cloud.config.server.git.username:访问git仓库的用户名 spring.cloud.config.server.git.password:访问git仓库的用户密码 Config Client 配置文件 spring.application.name:对应前配置文件中的{application}部分 spring.cloud.config.profile:对应前配置文件中的{profile}部分 spring.cloud.config.label:对应前配置文件的git分支 spring.cloud.config.uri:配置中心的地址 参考资料 (SpringCloud 中文官网)https://springcloud.cc/ (Spring Cloud Config)https://springcloud.cc/spring-cloud-config-zhcn.html (大神DD)http://blog.didispace.com/springcloud4/ 其他在Config Server中,还有一种不使用Git的”native”的配置方式,这种方式是从本地classpath 或文件系统中加载配置文件(使用 “spring.cloud.config.server.native.searchLocations”配置项进行设置)。 加载Config Server 的”spring.profiles.active=native”配置项可以开启native配置。如:12spring.profiles.active=nativespring.cloud.config.server.native.searchLocations=file:D:/properties 注意 : 牢记使用file:前缀来指示资源(默认没有前缀是从classpath中去文件)。也可以嵌入${}环境参数占位符,但是windows系统下使用绝对路径,前缀后面需要多加个”/“。","categories":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/categories/spring-cloud/"}],"tags":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/tags/spring-cloud/"}]},{"title":"断路器 Hystrix","slug":"断路器-Hystrix","date":"2017-02-19T13:18:06.000Z","updated":"2017-02-19T13:20:46.765Z","comments":true,"path":"2017/02/19/断路器-Hystrix/","link":"","permalink":"https://zggdczfr.cn/2017/02/19/断路器-Hystrix/","excerpt":"Spring Cloud Netflix Hystrix是分布式系统处理超时和错误的机制。当某个微服务发生故障时,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。","text":"Spring Cloud Netflix Hystrix是分布式系统处理超时和错误的机制。当某个微服务发生故障时,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。 断路器 Hystrix断路器模式 (云计算设计模式)断路器模式源于Martin Fowler的Circuit Breaker一文。在分布式环境中,其中的应用程序执行访问远程资源和服务的操作,有可能对这些操作的失败是由于瞬时故障,如慢的网络连接,超时,或者被过度使用的资源或暂时不可用。这些故障一般之后的短时间内纠正自己。所谓的断路器模式,就是当某个微服务发生故障时,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。(来源于网上资料) 什么是 Hystrix?Spring Cloud Netflix Hystrix是分布式系统处理超时和错误的机制。其服务失效判断阈值为 : 在Hystrix里默认是5秒内20个失败。 正常的分布式系统架构图(来源于Hystri官方文档)如下 : 若其中一个服务挂掉之后可能会导致其他调用业务服务的线程大量堵塞,最终导致整个系统瘫痪。Hystrix 允许开发人员提供错误提示信息并开启一个路由回调。如图(来源于Hystri官方文档) : 通过 Feign 使用 HystriFeign中已经依赖了Hystrix,所以我们直接调用即可,不必加入 Hystri 依赖。在上一次的 Fegin 工程中使用 Hystri。 创建回调类并实现调用接口类 12345678@Componentpublic class ServiceClientHystrix implements ServiceClient { @Override public String printf() { return \"断路器 : 回调函数\"; }} 使用@FeignClient注解中的fallback属性指定回调类 1234567@Component@FeignClient(value = \"my-service\", fallback = ServiceClientHystrix.class)public interface ServiceClient { @RequestMapping(\"/service\") String printf();} 为了能够看到回调效果,我们设置 Hystri 的超时时间为1毫秒。在 application.properties添加属性: 1hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1 依次启动”服务注册中心”、”服务提供者”、”服务消费者(Feign版)”,连续多次访问 http://localhost:2222/test 。 添加 Hystrix Dashboard 监控 添加依赖 123456789<!-- Hystrix Dashboard 监控 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId></dependency> 在启动类添加启动注解@EnableHystrixDashboard 再次启动”服务消费者”,访问 http://localhost:2222/hystrix 在URL中输入监控URL : http://localhost:2222/hystrix.stream 同时多次访问 http://localhost:2222/test 并观察监控仪表板的变化。 发现的问题 问题描述:Fegin第一次启动请求失败? 问题原因:首次请求往往会比较慢(Spring的懒加载机制,需要实例化一些类),因此请求时间往往大于 Hystrix 的默认超时时间(1秒)。 解决方法:延长 Hystrix 的超时时间1234/** * default 是默认所有请求;具体某个请求则修改 default 为相应的访问即可 */hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000 参考资料 https://github.com/Netflix/Hystrix/wiki https://springcloud.cc/spring-cloud-netflix-zhcn.html#true-circuit-breaker-hystrix-clients http://blog.didispace.com/springcloud3/ http://www.itmuch.com/spring-cloud-feign-ribbon-first-request-fail/ 个人参考项目个人参考项目 : https://github.com/FunriLy/springcloud-study/tree/master/%E6%A1%88%E4%BE%8B4","categories":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/categories/spring-cloud/"}],"tags":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/tags/spring-cloud/"}]},{"title":"Fegin的使用","slug":"Fegin的使用","date":"2017-02-18T13:03:01.000Z","updated":"2017-02-19T13:19:14.433Z","comments":true,"path":"2017/02/18/Fegin的使用/","link":"","permalink":"https://zggdczfr.cn/2017/02/18/Fegin的使用/","excerpt":"Feign : Declarative REST clients,是一个声明web服务客户端。Spring Cloud 集成 Ribbon 和 Eureka 提供的负载均衡的HTTP客户端 Feign。","text":"Feign : Declarative REST clients,是一个声明web服务客户端。Spring Cloud 集成 Ribbon 和 Eureka 提供的负载均衡的HTTP客户端 Feign。 Feign 的使用什么是Feign?Feign : Declarative REST clients。Feign 是一个声明web服务客户端,这便得编写web服务客户端更容易,使用 Feign 创建一个接口并对它进行注解,它具有可插拔的注解支持包括Feign注解与JAX-RS注解,Feign还支持可插拔的编码器与解码器,Spring Cloud 增加了对 Spring MVC的注解,Spring Web 默认使用了HttpMessageConverters, Spring Cloud 集成 Ribbon 和 Eureka 提供的负载均衡的HTTP客户端 Feign。(来源于Spring Cloud Netflix 官网文档) Feign 在 github 上的开源文档 : https://github.com/OpenFeign/feign/wiki 怎么使用Feign首先继续使用上次的”服务注册中心”与”服务提供者”。接下来,就是将 Fegin 整合到”服务消费者”中。 引入依赖1234<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId></dependency> 注入注解在 Spring Boot 主类加入注解@EnableFeignClients开启 Fegin 功能。 注入接口创建接口,具体如下:1234567@Component@FeignClient(value = \"my-service\")public interface ServiceClient { @RequestMapping(\"/service\") String printf();} 使用@Component注解向 Spring Boot 中注入该组件。 使用@FeignClient("")注解来绑定该接口对应的服务 通过 Spring MVC 的注解来配置服务下的具体实现。 在Controller中调用接口1234567891011@RestControllerpublic class ConsumerController { @Autowired private ServiceClient serviceClient; @RequestMapping(\"/test\") public String test(){ return serviceClient.printf(); }} 最后,启动”注册中心”、”服务提供者”、”服务消费者”等工程。访问 http://localhost:2222/test 就可以在”服务提供者”控制台查看到负载均衡的实现。 注意事项注意注意注意当Spring Cloud版本为 Brixton.RELEASE ,会抛出异常: Attribute ‘value’ in annotation [org.springframework.cloud.netflix.feign.FeignClient] must be declared as an @AliasFor [serviceId], not [name] 解决办法:将Spring Cloud版本改为 Brixton.SR5 或 Camden.RELEASE 即可。 参考资料 (大神DD的博客)http://blog.didispace.com/springcloud2/ (官方文档中文译本)https://springcloud.cc/spring-cloud-netflix-zhcn.html#spring-cloud-feign 个人参考项目个人参考项目 : https://github.com/FunriLy/springcloud-study/tree/master/%E6%A1%88%E4%BE%8B3","categories":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/categories/spring-cloud/"}],"tags":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/tags/spring-cloud/"}]},{"title":"使用Ribbon实现客户端的负载均衡","slug":"使用Ribbon实现客户端的负载均衡","date":"2017-02-13T10:54:08.000Z","updated":"2017-02-19T13:20:48.408Z","comments":true,"path":"2017/02/13/使用Ribbon实现客户端的负载均衡/","link":"","permalink":"https://zggdczfr.cn/2017/02/13/使用Ribbon实现客户端的负载均衡/","excerpt":"Spring Cloud Netflix Ribbon 是一个客户端负载均衡的组件。Ribbon的特点 和Eureka完美整合 支持多种协议-HTTP,TCP,UDP 缓存/批处理 built in failure resiliency","text":"Spring Cloud Netflix Ribbon 是一个客户端负载均衡的组件。Ribbon的特点 和Eureka完美整合 支持多种协议-HTTP,TCP,UDP 缓存/批处理 built in failure resiliency 使用Ribbon实现客户端的负载均衡RibbonSpring Cloud Netflix Ribbon 是一个客户端负载均衡的组件。 Ribbon的特点 和Eureka完美整合 支持多种协议-HTTP,TCP,UDP 缓存/批处理 built in failure resiliency 具体请查看官方文档 : https://github.com/Netflix/ribbon/wiki 整合Ribbon实现客户端的负载均衡前期准备这里用到了上次的两个demo(服务注册中心和服务提供者)。首先,添加服务提供者的服务,这里我将其端口号打印出来能够更加直观地观察到负载均衡的实现:123456789101112@RestControllerpublic class PrintfController { @Value(\"${server.port}\") private String port; @RequestMapping(\"/service\") public String printf(){ System.out.println(\"服务消费者正在使用服务,端口号为 : \"+port); return \"success\"; }} 接下来分别启动这两个工程,注意将服务提供者的端口号(原来为1111改为1112)修改后再启动一次.就整个项目而言,总共有三个微服务再运行着(一个注册中心,两个服务提供者)。PS:其实服务提供者你想弄多少都行。 创建服务消费者像一个普通的 spring cloud 工程一样,创建后向注册中心注册自己的信息。添加依赖:1234<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId></dependency> 添加消费调用接口:这里涉及到一个类LoadBalancerClient,这个就是由 Netflix Ribbon 提供的工具类。他会根据 ServiceId (配置文件中的Service Name)向 Eureka (注册服务器)获取服务地址。1234567891011121314@RestControllerpublic class ConsumerController { @Autowired private LoadBalancerClient client; @RequestMapping(\"/test\") public String test(){ ServiceInstance instance = client.choose(\"service\"); URI uri = instance.getUri(); System.out.println(uri); return (new RestTemplate()).getForObject(uri+\"/service\",String.class); }} 配置文件信息:1234# eureka client 配置spring.application.name=ribbon-consumerserver.port=2222eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/ 注意:LoadBalancerClient返回的是已经注册的服务器地址。 启动服务消费者启动工程后,我们可以看到如下注册信息: Ribbon 实现负载均衡多次访问 http://localhost:2222/test ,并查看控制台信息: 参考资料: Spring Cloud Netflix 官网文档-中文译本 Spring Cloud Netflix Ribbon 个人参考项目个人参考项目 : https://github.com/FunriLy/springcloud-study/tree/master/%E6%A1%88%E4%BE%8B2","categories":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/categories/spring-cloud/"}],"tags":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/tags/spring-cloud/"}]},{"title":"Eureka 学习笔记","slug":"Eureka-学习笔记","date":"2017-02-11T08:48:20.000Z","updated":"2017-02-19T13:18:38.931Z","comments":true,"path":"2017/02/11/Eureka-学习笔记/","link":"","permalink":"https://zggdczfr.cn/2017/02/11/Eureka-学习笔记/","excerpt":"Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务。Spring Cloud将它集成在其子项目spring-cloud-netflix中,以实现Spring Cloud的服务发现功能。在Spring Cloud中,将会使用 Eureka 作为发现服务,所以了解 Eureka 是非常重要的。","text":"Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务。Spring Cloud将它集成在其子项目spring-cloud-netflix中,以实现Spring Cloud的服务发现功能。在Spring Cloud中,将会使用 Eureka 作为发现服务,所以了解 Eureka 是非常重要的。 关于 Eureka 的学习笔记前言Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务。Spring Cloud将它集成在其子项目spring-cloud-netflix中,以实现Spring Cloud的服务发现功能。在Spring Cloud中,将会使用 Eureka 作为发现服务,所以了解 Eureka 是非常重要的。因此做了一下翻译以及学习笔记记录。Eureka的Github:https://github.com/Netflix/Eureka Eureka 笔记什么是 Eureka?Eureka 是基于 REST(Representational State Transfer)服务。主要用于AWS云中的定位/发现服务,从而实现对于中间层服务器的负载均衡(Load Balance)和故障切换(failover)。同时,Eureka 还提供了一个基于JAVA的客户端组件(Eurake Client),便于与Eureka Server进行交互。在客户端中,同样内置了负载均衡,用于执行基本的负载均衡轮询制度。而 Netflix 将它们集合成一个更加负载的负载均衡器,通过对流量、资源使用等因素,提供了更加合理的加权负载均衡策略服务。Eureka 具有心跳检测、健康检查和客户端缓存等多种机制提高了服务系统的灵活性。 什么是AWS云?对于AWS云,可以参考一下这篇博文 http://blog.csdn.net/awschina/article/details/17639191感觉 Eureka 就是为了提供对于 AWS云服务 的补充(中间层负载均衡)。 什么是中间层(Middle Tier)?上面老是提到了中间层(Middle Tier)。中间层 (Middle Tier)也称作“应用程序服务器层或应用服务层”,是用户接口或 Web 客户端与数据库之间的逻辑层。典型情况下 Web 服务器位于该层,业务对象在此实例化。(来源于百度百科) Eureka 目的/优点 中间层的负载均衡 使用 Netflix 的红/黑部署(red/black deployments),使开发者更加容易实现云部署 对于 cassandra deployments,方便于对实例化后的对象维护 利用 memcached 提供缓存服务(Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。)。 可配置性/动态配置。使用Eureka,可以即时添加或删除群集节点;Eureka使用archaius(Netflix开源的配置管理类):如果你有一个配置源实现,这些配置可以进行动态调整应用。 Eureka 最大的特性Eureka 集群服务具有极大的”弹性”(Resilience)。这个是我认为它最大的特点。 Eureka Client 处理一个或多个Eureka服务器的故障。 由于 Eureka Client 在其中具有注册表缓存信息,因此即使所有 Eureka Servers 都关闭,它们还是可以很好地运行。 Eureka Servers 即使在其他 Eureka 挂了也具有极大的”弹性”。既是是在 Client 与 Server 的网络分裂(network partition)期间,Eureka Server 具有的内部弹性特性也能防止大规模服务中断。 Eureka Server 与 Client 之间的通信Eureka 只是找到有关客户端与之通信的服务器的信息,但不对通信的协议或方法施加任何限制。通常情况下,我们可以使用 Eureka 获取目标服务器地址,并使用thrift,http(s)或任何其他RPC机制等协议来进行交互通信。 Eureka 架构接下来,对其中的名词进行解释: Application Service 相当于服务提供者 Application Client 相当于服务消费者 Make Remote Call,其实就是实现服务的使用 us-east-1 其实实现了Eurake 集群服务 us-east-1c、us-east-1d、us-east-1e 就是集群服务中的某个具体实现区域(感觉找不到一个合适的词来解释这种抽象的概念) Eureka 架构机制我们的 Eureka 集群服务其实就是靠 Server 与 Client 之间的交互来实现的。 前面说过,Eureka Server 具有服务定位/发现的能力,在各个微服务启动时,会通过Eureka Client向Eureka Server进行注册自己的信息(例如网络信息)。 一般情况下,微服务启动后,Eureka Client 会周期性向 Eureka Server 发送心跳检测(默认周期为30秒)以注册/更新自己的信息。 如果 Eureka Server 在一定时间内(默认90秒)没有收到 Eureka Client 的心跳检测,就会注销掉该微服务点。 同时,Eureka Server 它本身也是 Eureka Client,多个 Eureka Server 通过复制注册表的方法来完成服务注册表的同步从而达到集群的效果 参考资料 https://github.com/Netflix/eureka/wiki","categories":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/categories/spring-cloud/"}],"tags":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/tags/spring-cloud/"}]},{"title":"初学 Spring Cloud","slug":"初学Spring Cloud","date":"2017-02-10T02:44:20.000Z","updated":"2017-02-20T12:49:36.162Z","comments":true,"path":"2017/02/10/初学Spring Cloud/","link":"","permalink":"https://zggdczfr.cn/2017/02/10/初学Spring Cloud/","excerpt":"在SpringBoot的坑还没填完的情况下,我又迫不及待地开新坑了。主要是寒假即将结束了,到时又得忙于各种各样的事情……留个坑给自己应该就会惦记着它,再慢慢地补上…………………………SpringCloud,我来啦hhhhhhhh~","text":"在SpringBoot的坑还没填完的情况下,我又迫不及待地开新坑了。主要是寒假即将结束了,到时又得忙于各种各样的事情……留个坑给自己应该就会惦记着它,再慢慢地补上…………………………SpringCloud,我来啦hhhhhhhh~ 初学SpringCloudSpringCloud 介绍Spring Cloud是一个基于Spring Boot实现的云应用开发工具(就是说,接触Spring Cloud之前需要了解一下Spring Boot)。归结起来,Spring Cloud 是一个微服务工具包,为开发者提供了在分布式系统的配置管理、服务发现、断路器、智能路由、微代理、控制总线等许多开发工具包。按照官方的说法就是,Spring Cloud 为开发者提供了在分布式系统操作的工具。而且容易上手,可以进行快速开发。 微服务架构微服务架构概念“微服务架构”,就是将一个完整的应用从数据存储开始拆分成多个不同的服务,每个服务都能独立部署、独立维护、独立扩展。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。服务与服务间通过诸如RESTful API的方式互相调用。 关于微服务架构的扯淡“微服务架构”,其实这个概念好几年前就已经出现了,而且国外也出现了较为成熟的产品:netflix、dubbo等。听说目前 Spring 开发团队的精力主要集中于 spring boot 和 spring cloud 相关框架的开发。一般情况下,作为一个学习 java 的屌丝,基本上跟上 spring 屌丝的步伐,也就跟上了主流技术。 微服务架构的选择附上大神 程序猿DD(翟永超) 的一篇博客,可以参考一下:微服务架构的基础框架选择:Spring Cloud还是Dubbo? 服务注册与发现(Eureka)Eureka Server 创建一个 Spring Boot 项目。只要引入了Log4j2,便于日志记录。pom.xml依赖文件如下:1234567891011121314151617181920212223242526 <!-- SpringBoot 框架 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--log4j2--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <!-- eureka-服务 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> 版本参数:123456789101112131415161718192021 <!-- 还是比较喜欢稳定的 Brixton 版本 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Brixton.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!--spring boot 的 maven 插件--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> 通过@EnableEurekaServer注解启动一个服务注册中心。直接在SpringBoot启动类加上注解@EnableEurekaServer即可。 application.properties配置: 1234server.port=8761eureka.client.register-with-eureka=falseeureka.client.fetch-registry=falseeureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/ 同时,简要说明一下这些配置项:eureka.client.registerWithEureka :表示是否将自己注册到Eureka Server,默认为true。由于当前这个应用就是Eureka Server,故而设为false。eureka.client.fetchRegistry :表示是否从Eureka Server获取注册信息,默认为true。因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,故而设为false。eureka.client.serviceUrl.defaultZone :设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。默认是http://localhost:8761/eureka ;多个地址可使用 , 分隔。 启动工程,访问 http://localhost:8761/ 。如图所示: Eureka Client 创建一个 Spring Boot 项目。基本上与上一个工程无异,pom.xml需要修改了一个依赖。 12345 <!-- eureka-服务 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> 通过@EnableDiscoveryClient注解启动一个服务注册中心。直接在SpringBoot启动类加上注解@EnableDiscoveryClient即可。同时,Eureka Client 提供的服务与一般 Spring Boot 项目是一样的。 application.properties配置:这些配置参数还是比较容易理解的。 1234# eureka client 配置spring.application.name=service0server.port=1111eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/ 启动两个工程,并访问 Eureka Server 中心 http://localhost:8761/ 。如图所示: 附录个人参考项目 : https://github.com/FunriLy/springcloud-study/tree/master/%E6%A1%88%E4%BE%8B1 参考资料 (Spring Cloud Netflix) https://springcloud.cc/spring-cloud-netflix-zhcn.html","categories":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/categories/spring-cloud/"}],"tags":[{"name":"spring cloud","slug":"spring-cloud","permalink":"https://zggdczfr.cn/tags/spring-cloud/"}]},{"title":"java调用cmd编译(二)","slug":"java调用cmd编译C文件(二)","date":"2017-02-05T02:21:51.000Z","updated":"2017-02-18T13:14:17.308Z","comments":true,"path":"2017/02/05/java调用cmd编译C文件(二)/","link":"","permalink":"https://zggdczfr.cn/2017/02/05/java调用cmd编译C文件(二)/","excerpt":"利用java后台来编译运行C源文件,主要是为了开发一个在线编译网站的准备。这里主要介绍如何编译运行C源文件,其实对于其他语言,道理都是一模一样的。","text":"利用java后台来编译运行C源文件,主要是为了开发一个在线编译网站的准备。这里主要介绍如何编译运行C源文件,其实对于其他语言,道理都是一模一样的。 如何使用Java来编译运行C文件(二)前言码农的小日子过得好好的,指导老师一个兴起要求搞一个自己的在线编译网站,我们这种做小弟的只能老老实实地去搞。还好刚刚结束了考试与比赛,因为各种原因导致原定于寒假开工的项目延迟到下学期了,刚好趁这段空闲的时间来搞一搞。其实,自己感觉搞这个的话也挺好玩的~ 介绍利用java后台来编译运行C源文件,主要是为了开发一个在线编译网站的准备。这里主要介绍如何编译运行C源文件,其实对于其他语言,道理都是一模一样的。 开始动工批处理文件先填补上一篇博文剩下的坑。稍微解释一下情景,我想要cd到E盘,然后调用gcc来编译.c文件,可惜搜索借用网上关于使用&&来连接多条cmd语句不能成功,暂时我只想到利用批处理文件来实现这个功能了。123后缀名为.bat的文件,会被系统自动编译为批处理文件。*.bat 文件中输入,就相当于在dos 窗口中输入一样, 编辑完成后,双击即可运行。临时变量:在批处理文件 中设置临时变量方式:`set 变量名=变量值`, = 左右不要有空格,使用方式: %变量名% 即可。批处理文件执行完之后,窗口会自动关闭;若想执行完之后,窗口不自动关闭的话,在文件末尾添加 pause 即可。 调用批处理文件来实现编译1234Runtime run = Runtime.getRuntime();String filePath = \"bat文件绝对地址\";Process p = run.exec(\"cmd.exe /c \" + filePath);//这样子就实现了java调用执行多条cmd语句 编译C源文件将.bat文件与.c文件放于同一目录之下,调用Runtime.getRuntime().exec()来执行编译功能。对于编译来说,只要是获取有可能的错误提示,通过不断接收我们开启的进程的getErrorStream()来获得编译的错误提示。注意,这里没有涉及到我们构建的进程(会有三条不同的流:标准输入流、标准输出流、错误输入流)多条流之间的交互关系,所以理论上不需要使用waitFor()来获取结果。但在运行过程中就不得不使用这个方法来堵塞进程。waitFor()容易导致死锁的发生!123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 static Process c; /** * 通过 Runtime 调用批处理文件来编译编源文件 * 注意:批处理文件需要与编译源文件位于同一目录下 * @param filePath 批处理文件的绝对路径 * @throws FileNotFoundException 找不到 */ static public void compileApplication(String filePath) throws FileNotFoundException { Runtime run = Runtime.getRuntime(); String cPath = filePath.replace(\"bat\", \"c\"); File batFile = new File(filePath); File cFile = new File(cPath); if(!cFile.exists()){ throw new FileNotFoundException(\"找不到c编译源文件!\"); } if(batFile.exists()) { try { c = run.exec(\"cmd.exe /c \" + filePath); InputStream in = c.getInputStream(); BufferedInputStream errorIn = new BufferedInputStream(c.getErrorStream()); int ch; StringBuffer errortext = new StringBuffer(\"\"); //如果有编译错误,读取错误提示 while ((ch = errorIn.read()) != -1) { errortext.append((char) ch); } //将编译错误打印出来,并抛出错误异常 if (!errortext.equals(\"\")) { System.out.println(errortext); //自定义错误异常 } errorIn.close(); } catch (Exception e) { e.printStackTrace(); } finally { if (c != null) { c.destroy(); } } } else { throw new FileNotFoundException(\"找不到cmd批处理文件!\"); } } 如果编译失败时,我们就能从errortext中拿到编译失败提示: 运行exe文件上面一小节已经介绍了,如何编译一个C源文件。对一个编译功能来说,我们只要得到他的错误提示就行了,所以只是一直读取进程的错误信息。但运行一个exe文件时,我们不仅要往进程中输入参数、读取进程的标准输出和错误输出。现在,我们就要了解到进程的waitFor()函数。 通过查看JDK帮助文档,我们可以得知: 1. waitForpublic abstract int waitFor() throws [InterruptedException] 导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。如果没有终止该子进程,调用的线程将被阻塞,直到退出子进程。返回:进程的出口值。根据惯例,0 表示正常终止。抛出:[InterruptedException] - 如果当前线程在等待时被另一线程中断,则停止等待,抛出 [InterruptedException]。 2. 死锁情况同时,我通过查阅资料了解到:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入和从标准输入快速的读入都有可能造成子进程的所,甚至死锁。当Runtime对象调用exec()后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitFor()这里。为了避免这种情况的发生,我在执行exe文件的一个进程启动四条线程,分别对标准输入、标准输出、标准错误流进行读写,还有一个线程进行时间的控制。 代码如下:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 static Process p; /** * 过 Runtime 调用运行 exe 文件 * @param filePath exe 文件绝对路径 * @param inputString 程序读取数据 * @throws InterruptedException * @throws FileNotFoundException */ static public void openApplication(String filePath, final String inputString) throws InterruptedException, FileNotFoundException { File file = new File(filePath); if(!file.exists()){ throw new FileNotFoundException(\"找不到exe文件!\"); } try { p = Runtime.getRuntime().exec(filePath); //exe程序数据输出流,相当于进程标准输入流 final BufferedInputStream output = new BufferedInputStream(p.getInputStream()); //exe程序数据输入流 final BufferedOutputStream input = new BufferedOutputStream(p.getOutputStream()); //exe程序错误输出流 final BufferedInputStream errorOutput = new BufferedInputStream(p.getErrorStream()); final StringBuffer outputText = new StringBuffer(\"获得信息是: \\n\"); final StringBuffer errorText = new StringBuffer(\"错误信息是:\\n\"); /** * 向线程进行输入 */ new Thread(){ public void run(){ try { System.out.println(\"执行输入!\\n\"); //将用户输入数据写入 input.write(inputString.getBytes()); input.flush();//清空存缓 System.out.println(\"----\\n读入完毕\\n---\\n\"); } catch (IOException e) { e.printStackTrace(); } finally { if(input != null) { try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } } }.start(); /** * 获得输出的线程 */ new Thread(){ public void run(){ int ch; try { System.out.println(\"执行输出!\\n\"); //不断获取用户输出 while ((ch = output.read()) != -1) { outputText.append((char) ch); } } catch (IOException e) { e.printStackTrace(); } finally { if (output != null){ try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } }.start(); /** * 获得进程的错误提示 */ new Thread(){ public void run(){ int ch; try { System.out.println(\"执行错误输出!\\n\"); //不断获取错误输出 while ((ch = errorOutput.read()) != -1) { System.out.println((char) ch); errorText.append((char) ch); } } catch (IOException e) { e.printStackTrace(); } finally { if(errorOutput != null){ try { errorOutput.close(); } catch (IOException e) { e.printStackTrace(); } } } } }.start(); /** * 控制时间的进程 */ Thread timeController = new Thread(){ public void run(){ try { System.out.println(\"执行时间控制!\\n\"); Thread.sleep(5000); //限制运行时间 //加入错误提示信息 errorText.append(\"\\n运行时间过长!\\n\"); p.destroy(); } catch (InterruptedException e) { e.printStackTrace(); } finally { if(p != null) { p.destroy(); } } } }; timeController.start(); SimpleDateFormat df = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");//设置日期格式 //记录执行时间 System.out.println(\"\\n开始执行时间:\"+df.format(new Date()));// new Date()为获取当前系统时间 //一直等待直到“启动成功” int retval = p.waitFor(); //waitfor()结束后,关闭时间控制进程 timeController.stop(); //记录结束时间 System.out.println(\"\\n结束执行时间:\"+df.format(new Date()));// new Date()为获取当前系统时间 System.out.println(outputText); System.out.println(errorText); System.out.println(retval); } catch (IOException e) { e.printStackTrace(); } finally { if(p!=null) { p.destroy(); } } } 参考案例https://github.com/FunriLy/OnlineCompilation","categories":[{"name":"其他奇奇怪怪的","slug":"其他奇奇怪怪的","permalink":"https://zggdczfr.cn/categories/其他奇奇怪怪的/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"}]},{"title":"java调用cmd编译(一)","slug":"java调用cmd编译C文件(一)","date":"2017-02-05T02:21:51.000Z","updated":"2017-02-18T13:14:23.925Z","comments":true,"path":"2017/02/05/java调用cmd编译C文件(一)/","link":"","permalink":"https://zggdczfr.cn/2017/02/05/java调用cmd编译C文件(一)/","excerpt":"利用java后台来编译运行C源文件,主要是为了开发一个在线编译网站的准备。这里主要介绍如何编译运行C源文件,其实对于其他语言,道理都是一模一样的。","text":"利用java后台来编译运行C源文件,主要是为了开发一个在线编译网站的准备。这里主要介绍如何编译运行C源文件,其实对于其他语言,道理都是一模一样的。 如何使用Java来编译运行C文件(一)前言码农的小日子过得好好的,指导老师一个兴起要求搞一个自己的在线编译网站,我们这种做小弟的只能老老实实地去搞。还好刚刚结束了考试与比赛,因为各种原因导致原定于寒假开工的项目延迟到下学期了,刚好趁这段空闲的时间来搞一搞。其实,自己感觉搞这个的话也挺好玩的~ 介绍利用java后台来编译运行C源文件,主要是为了开发一个在线编译网站的准备。这里主要介绍如何编译运行C源文件,其实对于其他语言,道理都是一模一样的。 前期技术准备1. 调用cmd编译C文件先说明一下,我的操作系统是Win10,Linux环境下会有所不同;而编译环境是GCC。 打开命令行界面并确认GCC可用123键盘win + r ( 这里的r是run的意思)输入cmd,回车,你就看到了命令行输入gcc -v --如果返回了结果,那么继续,如果不识别,那么请立刻参考“附录” 如: 写好C语言源文件创建test.c文件,输入以下代码:123456#include<stdio.h>int main(){ printf(\"hello world\\n\"); getchar(); rerturn 0;} 将.c文件放于E盘中,方便操作。 编译该.c文件12E: cdgcc test.c -o test 这里的cd是change directory的意思 接下来,在E盘中就能找到test.exe文件,双击运行即可。备注:也可通过命令行来运行该文件! 附录当你输入gcc时,之所以你看到了:1不是内部或外部命令,也不是可运行的程序或批处理文件。 因为你没有在自己的环境变量之中添加gcc.exe的路径。于是系统完全不知道去哪里寻找gcc.exe。关于GCC的安装配置我就不废话了,直接借用CSDN上firefoxbug大神的博文,附上链接Windows下安装配置GCC编译器 2. Java运行命令行java的Runtime.getRuntime().exec(string)可以调用执行cmd指令。123456789cmd /c dir 是执行完dir命令后关闭命令窗口。cmd /k dir 是执行完dir命令后不关闭命令窗口。cmd /c start dir 会打开一个新窗口后执行dir指令,原窗口会关闭。cmd /k start dir 会打开一个新窗口后执行dir指令,原窗口不会关闭。可以用cmd /?查看帮助信息。 附上一个简单的调用demo12345678910111213 public static void main(String[] args) { String string = \"要执行的cmd语句\"; Runtime run = Runtime.getRuntime(); try { Process process = run.exec(\"cmd.exe /k start \" + string); /* 对进程 process 进行操作 */ process.destroy(); } catch (IOException e) { e.printStackTrace(); } } 关于多条cmd语句的执行问题在许多情况下,只调用一条cmd语句明显不能满足我们的需求,需要多条cmd语句来共同配合使用。关于网上使用&&来连接多条cmd语句的调用使用,本人亲测不成功,也有可能是我的操作有错(可与网上案例一模一样的代码运行不成功我也没办法啊,也考虑过是Windows操作系统的影响)。最后,我是使用了bat批处理文件来解决这个问题的,具体案例请参考我的开发案例。 常见的CMD命令注意:以下命令均由互联网收集而来!!!1. gpedit.msc—–组策略2. sndrec32——-录音机3. Nslookup——-IP地址侦测器4. explorer——-打开资源管理器5. logoff———注销命令6. tsshutdn——-60秒倒计时关机命令7. lusrmgr.msc—-本机用户和组8. services.msc—本地服务设置9. oobe/msoobe /a—-检查XP是否激活10. notepad——–打开记事本11. cleanmgr——-垃圾整理12. net start messenger—-开始信使服务13. compmgmt.msc—计算机管理14. net stop messenger—–停止信使服务15. conf———–启动netmeeting16. dvdplay——–DVD播放器17. charmap——–启动字符映射表18. diskmgmt.msc—磁盘管理实用程序19. calc———–启动计算器20. dfrg.msc——-磁盘碎片整理程序21. chkdsk.exe—–Chkdsk磁盘检查22. devmgmt.msc— 设备管理器23. regsvr32 /u .dll—-停止dll文件运行24. drwtsn32—— 系统医生25. rononce -p —-15秒关机26. dxdiag———检查DirectX信息27. regedt32——-注册表编辑器28. Msconfig.exe—系统配置实用程序29. rsop.msc——-组策略结果集30. mem.exe——–显示内存使用情况31. regedit.exe—-注册表32. winchat——–XP自带局域网聊天33. progman——–程序管理器34. winmsd———系统信息35. perfmon.msc—-计算机性能监测程序36. winver———检查Windows版本37. sfc /scannow—–扫描错误并复原38. taskmgr—–任务管理器(2000/xp/200339. winver———检查Windows版本40. wmimgmt.msc—-打开windows管理体系结构(WMI)41. wupdmgr——–windows更新程序42. wscript——–windows脚本宿主设置43. write———-写字板44. winmsd———系统信息45. wiaacmgr——-扫描仪和照相机向导46. winchat——–XP自带局域网聊天47. mem.exe——–显示内存使用情况48. Msconfig.exe—系统配置实用程序49. mplayer2——-简易widnows media player50. mspaint——–画图板51. mstsc———-远程桌面连接52. mplayer2——-媒体播放机53. magnify——–放大镜实用程序54. mmc————打开控制台55. mobsync——–同步命令56. dxdiag———检查DirectX信息57. drwtsn32—— 系统医生58. devmgmt.msc— 设备管理器59. dfrg.msc——-磁盘碎片整理程序60. diskmgmt.msc—磁盘管理实用程序61. dcomcnfg——-打开系统组件服务62. ddeshare——-打开DDE共享设置63. dvdplay——–DVD播放器64. net stop messenger—–停止信使服务65. net start messenger—-开始信使服务66. notepad——–打开记事本67. nslookup——-网络管理的工具向导68. ntbackup——-系统备份和还原69. narrator——-屏幕“讲述人”70. ntmsmgr.msc—-移动存储管理器71. ntmsoprq.msc—移动存储管理员操作请求72. netstat -an—-(TC)命令检查接口73. syncapp——–创建一个公文包74. sysedit——–系统配置编辑器75. sigverif——-文件签名验证程序76. sndrec32——-录音机77. shrpubw——–创建共享文件夹78. secpol.msc—–本地安全策略79. syskey———系统加密,一旦加密就不能解开,保护windows xp系统的双重密码80. services.msc—本地服务设置81. Sndvol32——-音量控制程序82. sfc.exe——–系统文件检查器83. sfc /scannow—windows文件保护84. tsshutdn——-60秒倒计时关机命令3. 84. tsshutdn——-60秒倒计时关机命令85. tourstart——xp简介(安装完成后出现的漫游xp程序)86. taskmgr——–任务管理器87. eventvwr——-事件查看器88. eudcedit——-造字程序89. explorer——-打开资源管理器90. packager——-对象包装程序91. perfmon.msc—-计算机性能监测程序92. progman——–程序管理器93. regedit.exe—-注册表94. rsop.msc——-组策略结果集95. regedt32——-注册表编辑器96. rononce -p —-15秒关机97. regsvr32 /u .dll—-停止dll文件运行98. regsvr32 /u zipfldr.dll——取消ZIP支持99. cmd.exe——–CMD命令提示符100. chkdsk.exe—–Chkdsk磁盘检查101. certmgr.msc—-证书管理实用程序102. calc———–启动计算器103. charmap——–启动字符映射表104. cliconfg——-SQL SERVER 客户端网络实用程序105. Clipbrd——–剪贴板查看器106. conf———–启动netmeeting107. compmgmt.msc—计算机管理108. cleanmgr——-垃圾整理109. ciadv.msc——索引服务程序110. osk————打开屏幕键盘111. odbcad32——-ODBC数据源管理器112. oobe/msoobe /a—-检查XP是否激活113. lusrmgr.msc—-本机用户和组114. logoff———注销命令115. iexpress——-木马捆绑工具,系统自带116. Nslookup——-IP地址侦测器117. fsmgmt.msc—–共享文件夹管理器118. utilman——–辅助工具管理器119. gpedit.msc—–组策略120. explorer——-打开资源管理器","categories":[{"name":"其他奇奇怪怪的","slug":"其他奇奇怪怪的","permalink":"https://zggdczfr.cn/categories/其他奇奇怪怪的/"}],"tags":[{"name":"java","slug":"java","permalink":"https://zggdczfr.cn/tags/java/"}]},{"title":"大学有感","slug":"大学有感","date":"2017-02-02T11:35:54.000Z","updated":"2017-02-07T08:49:04.040Z","comments":true,"path":"2017/02/02/大学有感/","link":"","permalink":"https://zggdczfr.cn/2017/02/02/大学有感/","excerpt":"","text":"“天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。”一直以来很喜欢这句话,一直以来无法全心全意投注于自己专注的事情之中,也一直以来朝着话中的信念前进。 在2015年高考惨遭”滑铁卢战役”,可能一直以来我都处于一种”不服”的观念之中。也正是这种”不服”,我总是尽可能地将我的工作做得更好。正如我朋友曾经对我说过,“你总是那么忙,忙于学习、忙于各种任务……”其实,忙一点,还是不错的。在大学一年半的时间内,我完成了3/6自己订下的目标。到今天,我仍然庆幸自己没有堕落,自己能加入一个能”拼命”的团队(QG)中学习如何与他人合作并且不断提高自己,自己仍然能按照自己选择的道路不断挣扎,虽然累可还是在承受范围内。 大学生活以来没有以前想象中那么高端美好,可每个人多多少少还是会有自己的收获。我想,生活本该如此,该玩的时候玩,该静心的时候静心,该学习的时候学习。大学乃至社会之中,各种形形色色、奇奇怪怪的人,自然有他存在的理由,又何必因为他人而轻易影响到自己?与人合作,只要不影响到我的进度,我可以容纳你的各种想法行为,其实是还未能打动我或者说是懒得理你……这就是我今天总结的”生存之道”。 正所谓,人静而后安,安而能后定,定而嫩=能后慧,慧而能后悟,悟而能后得。 ————《大学》","categories":[{"name":"随记感悟","slug":"随记感悟","permalink":"https://zggdczfr.cn/categories/随记感悟/"}],"tags":[]}]}