0%
jvm命令
本文字数: 928 阅读时长 ≈ 1 分钟
JVM命令的使用
https://blog.csdn.net/wangxiaotongfan/article/details/82560739
JPS
介绍
显示当前系统的java进程。
格式
1 |
参数
- -q:只显示pid,不显示class名称,jar文件名和传递给main方法的参数。
- -l:输出应用程序main class的完整package名或者应用程序的jar文件完整路径名。
- -m:输出jvm启动时传递给main方法启动。
- -v:输出JVM启动时显示指定的JVM参数。
JSTAT
介绍
用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译(Just In Time Compiler, 即时编译器)等运行数据。
格式
1 |
参数
1 | if [ "$DEBUG" = "true" ]; then |
一致性hash
本文字数: 719 阅读时长 ≈ 1 分钟
Spring AOP入门
发表于 更新于
本文字数: 11k 阅读时长 ≈ 10 分钟
1、AOP
1.1、概念
AOP 为面向切面编程,采用动态代理实现。
1.2、优点
采用动态代理的方式,可以增强原有的目标类的方法,我们可以在目标方法执行前后分别做一些事情。
对于 aop 就可以在 5 种通知里做一些事情,比如说数据库连接的释放,日志的打印,事务的操作。
这种方式,使得不用修改原有程序,就可以增加功能,降低了耦合。
1.3、结构
2、AOP 入门案例
2.1、AOP 前
AOP 采用动态代理实现,故很有必要在看 AOP 前先看动态代理的实现。动态代理分为两种,对于有接口,有实现类的可以采用 jdk 动态代理,对于没有接口,只有实现类的,需要采用 cglib 代理。
2.1.1、依赖包
1 | <dependencies> |
2.2、jdk 动态代理(AOP 手动方式)
jdk 动态代理中,代理类需要实现目标类的所有接口。
2.2.1、目标类:接口+实现类
接口:
1 | public interface UserService { |
实现类:
1 | public class UserServiceImpl implements UserService { |
2.2.2、切面类:用于保存通知
1 | public class MyAspect { |
2.2.3、代理类:生成代理对象
1 | public class MyBeanFactory { |
2.2.4、测试类:
1 | @Test |
2.3、cglib 代理放式
cglib 代理适用于没有接口只有实现类的情况。cglib 是通过让代理类继承目标类,从而增强目标类的方法。
2.3.1、目标类等同于 jdk 动态代理的接口的实现类
2.3.2、切面类同上
2.3.3、测试类同上
2.3.4、代理类
1 | public class MyBeanFactory { |
2.4、AOP 半自动代理
半自动代理采用 spring 提供的 org.springframework.aop.framework.ProxyFactoryBean 类(一个代理工厂)生成代理对象
2.4.1、目标类与 jdk 动态代理相同
2.4.2、切面类:环绕通知(此处与其他不同)
1 | public class MyAspect implements MethodInterceptor |
2.4.3、配置文件 beans.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
2.4.4、测试类
1 | @Test |
2.4.5、结果
before
addUser
after
before
updateUser
after
before
deleteUser
after
2.5、AOP 全自动代理
全自动代理仅需要在 beans.xml 中配置一下就好,使用aop:config。
spring 会自动判断使用那种代理方式,有接口时采用 jdk 方式,没有接口时采用 cglib 方式。
2.5.1、目标类与 jdk 动态代理相同
2.5.2、测试类与半自动方式相同
2.5.3、切面类
AOP 提供了 5 中通知类型,分别为,前置通知,返回通知,异常通知,最终通知,环绕通知,其中环绕通知最为强大。
除环绕通知,其他可以没有参数。
1 | public class MyAspect { |
2.5.4、配置文件 beans.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
2.5.5、结果
因为环绕通知中有 return 语句,故执行顺序和配置有关。
2.5.5.1、配置环绕通知时
before 环绕通知——–>前 addUser
afterafter 环绕通知———->后 afterreturning1
before 环绕通知——–>前 deleteUser
afterafter 环绕通知———->后 afterreturningnull
before 环绕通知——–>前 updateUser
afterafter
throwable / by zero
2.5.5.2、不配置环绕通知
before
addUser
afterreturning1
afterafter
before
deleteUser
afterreturningnull
afterafter
before
updateUser
throwable / by zero
afterafter
2.5.5.3、其他
异常通知,只有在产生异常后才会执行,此时,返回通知会被异常挡住不在执行。
返回通知可以拿到目标类方法拿到的返回值,如果没有返回值,就是 null。
环绕通知会使得最终通知提前。类似于,return 执行前,finally 块中的会先执行。
2.5.6、补充
全自动方式也可将切面做成类似于半自动的形式。
切面类:
前置通知:
1 | public class MyAspectBefore implements MethodBeforeAdvice { |
环绕通知:
1 | public class MyAspectMethod implements MethodInterceptor { |
配置方式:
1 | <aop:config proxy-target-class="true"> |
集合整理及基础操作
发表于 更新于
本文字数: 4.3k 阅读时长 ≈ 4 分钟
集合整理及基础操作
1、接口关系整理
集合顶层分为两大接口,collection,map
collection:单列元素集合的顶层接口。list 与 set 接口继承 collection 接口。
map:双列元素集合的顶层接口。
由此形成集合的三大类型。list、set、map。
Queen,使用较少,暂时不说有时间补上。
2、map 介绍
map 作为双列集合的跟接口,以键值对的形式存放元素。
java 中不提供对 map 接口的直接实现。使用其实现类构造对象。
主要使用的实现类有 HashMap 和 TreeMap。
2.1、HashMap
2.1.1、增删等操作
使用 put(),putAll()方法增加数据。put 方法将一个个键值对存放入 map 中,而 putAll 则将另一个集合添加进当前集合中。
putAll()方法添加的是集合的元素,会将一个集合中的元素添加到另一个集合中。
1 | Map m = new HashMap();m.put("1", "1"); |
使用 get()方法可以根据指定的键获相应的值。
1 | Object o = m.get("1"); |
clear()方法则可以清空 map 集合中的数据。
1 | m.clear(); |
使用 contansKey()和 cotainsValue()则可以分别根据 key 和 value 判断集合中是否包含该元素。
1 | boolean b = m.containsKey("1");boolean b1 = m.containsValue("1"); |
使用 keySet()可以获取该 map 中所有 key 的集合。将其放入一个 set 集合中。
1 | Set keyset = m.keySet(); |
使用 value()方法可以获取 map 中所有的值。不存在键值关系。
1 | Collection values = m.values(); |
返回此映射中包含的映射关系的 Set 视图。不好理解,迭代时会用到。
1 | Set entryset = m.entrySet(); |
2.1.2、源码和原理
2.2、TreeMap
2.2.1、treemap 与 hashmap 的区别
treemap 追溯源头,treemap 不仅实现了 map 接口,也是实现了,sortedmap 接口。treemap 是有序的而 hashmap 无序。
treemap 是红黑树基于 sortedmap 接口的实现。而 hashmap 则根据键的 hashcode 存放数据。
2.2.2、增删等操作
因为实现了 map 接口,普通基础操作与 hashmap 相同。只是多了从 sortedmap 实现得来的排序能力。
插入 treemap 的键必须是可以比较的,即实现了 Comparable 接口,否则会抛出异常。
插入的键为自定义对象时需要实现 Comparable 接口,重写其中的方法。
同样可以自定义 treemap 的排序规则。
1 | Map<Integer,Integer> m2 = new TreeMap(new Comparator() { @Override |
2.2.3、源码和原理
3、list 介绍
list 继承了 collection 接口,是 ArrayList,LinkedList 等 List 集合的跟接口。list 集合是单列集合。
因为 list 底层采用链表或数组实现,所以 list 是一种无序的集合。
3.1、ArrayList
3.1.1、增删改等操作
采用 add()方法添加元素添加为 list 的最后一个元素,也可以将元素添加到指定位置。添加指定位置时,按照索引地址添加,从 0 开始。
采用 addAll()方法可以将另一个集合(实现了 Collection)整体添加进来。也可以添加到指定位置。
1 | List l1 = new ArrayList(); |
使用 get()方法根据元素的下标获取元素的值。
1 | Object o = l1.get(1); |
使用 clear()方法可以清楚集合中的元素。
1 | l1.clear(); |
contains()方法和 containsAll()方法用于判断该集合中是否包含指定的元素,或集合。
1 | boolean contains = l1.contains("1"); |
isEmpty()方法用于判断该集合是否为空。
1 | boolean empty = l1.isEmpty(); |
使用 remove()方法可以移除集合中的元素,可以直接移除也可以根据索引移除。
1 | l1.remove(3); |
使用 set()方法可以为集合中指定位置的元素设置值。
1 | l1.set(1,"a"); |
list 虽然无序,但是可以使用 sort()方法对其排序,需要传入自定义的比较器。会按照比较器规则排序。
1 | l1.sort(new Comparator() { @Override |
3.1.2、源码和原理
3.2、LinkedList
3.2.1、与 ArrayList 区别
1、ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。
2、对于随机访问 get 和 set,ArrayList 觉得优于 LinkedList,因为 LinkedList 要移动指针。 3.、对于新增和删除操作 add 和 remove,LinedList 比较占优势,因为 ArrayList 要移动数。
3.2.2、增删改等操作
普通基础操作与 ArrayList 基本相同,只是实现原理不同。
LinkedList 与 ArrayList 相比,多了一部分在链表头和尾操作的方法。
增加了增加,获取,移除的方法。
1 | l1.addFirst("1"); |
使用 offer(),offerLast(),offerFirst()方法,添加元素,可以获取返回值绝对为 true。
1 | l1.offer("1"); |
使用 peek(),peekLast(),peekFirst()获取元素头节点尾节点的值,不会移除节点。
1 | Object peek = l1.peek(); |
使用 poll(),pollLast(),pollFirst()获取元素头节点尾节点的值,会移除节点。
1 | Object poll = l1.poll(); |
3.2.3、源码和原理
4、Set 介绍
4.1、HashSet
4.1.1、增删改等操作
使用 add()方法添加元素。
1 | HashSet set = new HashSet();set.add("1"); |
使用 contains()方法判断集合中是否存在该元素。
1 | boolean contains = set.contains("1"); |
使用 remove()方法移除集合中的该元素。
1 | boolean remove = set.remove("1"); |
使用 isEmpty()方法判断集合元素是否为空。
1 | boolean empty = set.isEmpty(); |
使用 size()方法获取集合元素个数。
1 | int size = set.size(); |
使用 clear()方法清空集合中的元素。
1 | set.clear(); |
4.1.2、源码和原理
4.2、TreeSet
4.2.1、与 HashSet 区别
与 HashSet 相似,TreeSet 底层同样采用 TreeMap 实现。同样具备 HashSet 不具备的排序能力。
底层使用 TreeMap 存放 key 的一列,而基于红黑树实现。
4.2.2、增删改等操作
TreeSet 实现了与 HashSet 相同的 Set 接口,其基础操作相同。
因为其具备排序能力,所以在 TreeSet 存储自定义对象时,自定义对象需要实现 Comparator 接口,重写其中的方法。
需要自定义排序规则。
1 | TreeSet t = new TreeSet(new Comparator() { |
4.2.3、源码和原理
集合并发修改异常
发表于 更新于
本文字数: 3k 阅读时长 ≈ 3 分钟
集合并发修改异常
先看例子
在对集合进行迭代器遍历时,在遍历过程中对集合中元素进行增加或删除,在删除数据之后会报并发修改异常。
原因:
在迭代过程中,不能修改集合结构。
我们采用 hasnext()方法判断集合是否还有元素可遍历,采用 next()方法获取元素。而 next()方法会每次检查集合结构是否发生变化。
1 | public E next() { |
next()方法会在第一行调用 checkForComodification()方法检测集合结构是否发生变化。如果发生变化则会报异常。
1 | final void checkForComodification() { |
checkForComodification()方法会检查集合结构是否发生变化。
cursor 是下一次要取的元素下标,next()方法执行后,cursor 就会往后移动一下。
这里modCount是 ArrayList 继承自 AbstractList 的变量,用来记录集合元素个数发生变化(增加和删除)的次数。
expectedModCount是类 Itr 的变量,是迭代器记录集合元素个数变化的次数。如下图所示:
1 | public Iterator<E> iterator() { |
在调用 iterator()方法时会返回一个 Itr 类的对象,同时会创建变量 expectedModCount,并将 modCount 传递给它,此时这两个值相等。
1 | public E remove(int index) { |
1 | public boolean add(E e) { |
1 | private void ensureCapacityInternal(int minCapacity) { |
而我们在对集合中添加或删除元素时,都会改变 modCount 的值,如上图所示:
当添加或删除这个操作在迭代过程中时,next()方法检查到 modCount 与 expectedModCount 值不一样,就会报并发修改异常。
那为什么迭代器自己的方法不会报异常?如下所示:
1 | public void remove() { |
我们发现迭代器自己的 remove()方法底层还是调用集合的 remove()方法,但比集合的 remove()方法多了一个在删除过后修改 expectedModCount 变量的过程。
使得删除后 expectedModCount 和 modCount 的值相同,这样在下一次 next()方法时,就不会报异常。
并发修改异常的意义在于保证迭代器获取到正确的元素。
解决办法:
1、在迭代器中不使用集合的增加删除操作。修改操作没有影响(修改操作不会改变集合元素的个数)。
2、当需要在迭代过程中需要对集合增加元素时,可是采用 Iterator 的子类 ListIterator,采用 listIterator()方法,该方法会返回一个 ListItr 类(仅支持 List)的对象,该类提供了反向遍历及增加的方法。
其他:
1、当 next()方法获取到的是集合的倒数第二个元素的时候,使用集合的删除操作不会发生异常
此时是因为:
当遍历到倒数第二个元素时,此时 cursor 的值为 size-1(倒数第二个元素的下标索引为 size-2,next()方法执行后 cursor 会加 1),而在此时进行删除操作后,size 的值会减一,变的和 cursor 相同。
1 | public boolean hasNext() { |
程序继续执行,hasnext()方法判断 cursor 和 size 相同,会返回 false,集合遍历将会终止。
2、在 foreach 循环遍历集合时中也不能采用集合的增加删除方法。因为 foreach 循环底层也是通过迭代器进行遍历,如果修改,同样会报异常。
3、**
缓存穿透,击穿,雪崩
发表于 更新于
本文字数: 553 阅读时长 ≈ 1 分钟
缓存穿透,击穿,雪崩
1、缓存穿透
概念:
正常情况下,需要查询的数据都存在,当查询一个缓存和数据库都不存在的数据时,每次请求都会落在数据库里,这种情况成称为缓存穿透。
问题:
缓存穿透一般会导致数据库压力增大。恶意攻击会击垮数据库。会绕过缓存。
解决:
1、在接口增加参数校验,不合法的直接返回。
2、缓存空值,将对应 key 的 value 设置为空值,避免暴力攻击。同时将 key 失效时间设置短一些,避免影响正常使用。
3、在网关阈值,限制同 ip 访问量。
4、高级用户布隆过滤器。bloom filter,可以对 key 进行判断是否在数据库存在,不存在就直接返回,存在就查询出来,并刷新缓存。
2、缓存击穿
概念:
高并发系统中,大量请求一般会落在缓存中,但在某一时刻这个热点 key 过期了,此刻大量请求就会落在数据库。
问题:
会导致数据库压力增大,严重者击垮数据库。
解决:
1、设置热点 key 不过期。
2、加上分布式锁,每次只有拿到锁的线程可以去访问数据库。第一个线程查询到后就会缓存起来,后面线程从缓存中拿。
3、缓存雪崩
概念:
某一时刻发生大规模缓存不可用问题,比如宕机,过期。
问题:
轻则查询变慢,重则大面积服务不可用。
解决:
1、采用分布式集群,减少宕机风险。
2、将 key 的失效时间设置为随机数,避免大量缓存同时失效。
3、采用本地缓存加限流逻辑。
多线程间通信
发表于 更新于
本文字数: 1.9k 阅读时长 ≈ 2 分钟
多线程间通信
1、案例
1 | package com.company; |
2、分析
此案例
多线程创建
发表于 更新于
本文字数: 2.6k 阅读时长 ≈ 2 分钟
多线程创建
1、线程的创建
1.1、继承 Tread 类实现
1 | public class myThread { |
1.2、实现 Runnable 接口
1 | public class myThreadtwo { |
1.3、实现 Callable 接口
1 | public class myThreadThree { |
2、创建方式的比较
如果一个类继承 Thread,则不适合资源共享。但是如果实现了 Runable 接口的话,则很容易的实现资源共享。
相对来说,实现 Runnable 接口比继承 Thread 好。个人感觉完成同一个任务实现接口比较方便,跑不同的任务,继承 Thread 比较方便。
采用匿名内部类使用 Runnable 接口,会隐式的印用当前 Activity,会造成内存泄露。
1、实现 Runnable 的优势
可以避免 java 中的单继承的限制
线程池只能放入实现 Runable 或 callable 类线程,不能直接放入继承 Thread 的类
2、两者都有的
适合多个相同的程序代码的线程去处理同一个资源
增加程序的健壮性,代码可以被多个线程共享,代码和数据独立