0%

文件操作

移动文件/重命名文件

1
2
3
4
5
6
# 移动文件
mv [源文件目录] [目标文件目录]

# 重命名文件
cd [目标目录]
mv [源文件名] [目标文件名]

复制文件

1
cp [源文件目录/源文件名] [目标文件目录]

删除文件

1
2
cd [目标文件目录]
rm -rf [目标文件名]

JVM命令的使用

https://blog.csdn.net/wangxiaotongfan/article/details/82560739

JPS

介绍

  显示当前系统的java进程。

格式

1

参数

  1. -q:只显示pid,不显示class名称,jar文件名和传递给main方法的参数。
  2. -l:输出应用程序main class的完整package名或者应用程序的jar文件完整路径名。
  3. -m:输出jvm启动时传递给main方法启动。
  4. -v:输出JVM启动时显示指定的JVM参数。

JSTAT

介绍

  用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译(Just In Time Compiler, 即时编译器)等运行数据。

格式

1

参数

1
2
3
4
5
6
7
8
if [ "$DEBUG" = "true" ]; then
echo -e "\033[0;31m debug is open,port=$DEBUG_PORT \033[0m"
JAVA_OPTS="$JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=y"
fi

JAVA_HEAP_DUMP_OPTS=-XX:HeapDumpPath=$LOG_HOME/dump.hprof
JAVA_OPTS="$JAVA_OPTS -Dserver.port=$PORT -Dlog.home=$LOG_HOME -Duse.flume=false -Xms128m -Xmx2048m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=1024m -Dfile.encoding=utf-8 -XX:+HeapDumpOnOutOfMemoryError $JAVA_HEAP_DUMP_OPTS"
#JAVA_OPTS="$JAVA_OPTS -XX:+TraceClassLoading -XX:+TraceClassUnloading"

介绍

  IO是输入输出,IO是面向流的。
  java中有三种IO模型: BIO,NIO,AIO。它们又可以分为同步异步,阻塞与非阻塞。
  而在linux中有5中IO模型。

阅读全文 »

介绍

  在分布式系统中,通常不同的机器存储着不同的数据,例如redis集群中存储的数据。
  这些数据通常采用hash算法来计算出应该存储在那台机器上。但是,当集群中的节点增加或者减少时,通过原有的hash算法计算出来的位置将会完全错误。
  一致性hash就是为了解决这种问题诞生的。

阅读全文 »

1、AOP

1.1、概念

AOP 为面向切面编程,采用动态代理实现。

1.2、优点

采用动态代理的方式,可以增强原有的目标类的方法,我们可以在目标方法执行前后分别做一些事情。
对于 aop 就可以在 5 种通知里做一些事情,比如说数据库连接的释放,日志的打印,事务的操作。
这种方式,使得不用修改原有程序,就可以增加功能,降低了耦合。

1.3、结构

2、AOP 入门案例

2.1、AOP 前

AOP 采用动态代理实现,故很有必要在看 AOP 前先看动态代理的实现。动态代理分为两种,对于有接口,有实现类的可以采用 jdk 动态代理,对于没有接口,只有实现类的,需要采用 cglib 代理。

2.1.1、依赖包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<dependencies>
<dependency>
<artifactId>org.springframework</artifactId>
<groupId>spring-core</groupId>
<version>3.2.7.RELEASE</version>
</dependency>
<dependency>
<artifactId>org.springframework</artifactId>
<groupId>spring-context</groupId>
<version>3.2.7.RELEASE</version>
</dependency>
<dependency>
<artifactId>org.springframework</artifactId>
<groupId>spring-beans</groupId>
<version>3.2.7.RELEASE</version>
</dependency>
<dependency>
<artifactId>org.springframework</artifactId>
<groupId>spring-expression</groupId>
<version>3.2.7.RELEASE</version>
</dependency>
<dependency>
<artifactId>org.springframework</artifactId>
<groupId>spring-aop</groupId>
<version>3.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
<version>1.1</version>
</dependency>

2.2、jdk 动态代理(AOP 手动方式)

jdk 动态代理中,代理类需要实现目标类的所有接口。

2.2.1、目标类:接口+实现类

接口:

1
2
3
4
5
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}

实现类:

1
2
3
4
5
6
7
8
9
10
11
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("addUser");
}
public void updateUser() {
System.out.println("updateUser");
}
public void deleteUser() {
System.out.println("deleteUser");
}
}

2.2.2、切面类:用于保存通知

1
2
3
4
5
6
7
8
public class MyAspect {
public void before() {
System.out.println("brfore");
}
public void after() {
System.out.println("after");
}
}

2.2.3、代理类:生成代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MyBeanFactory {
public static UserService createService() {
// 1、目标类
final UserService userService = new UserServiceImpl();
// 2、切面类
final MyAspect myAspect = new MyAspect();
// 3、代理对象
// 参数1、类加载器
当前类.class.getClassLoader()
目标类对象.getClass().getClassLoader()
// 参数2、目标类所实现的所有接口
目标类对象.getClass().getInterfaces()
new Class[]{接口.class}
// 参数3、InvocationHandler处理类,采用匿名内部类实现
UserService proxyService = (UserService) Proxy.newProxyInstance(MyBeanFactory.class.getClassLoader(),
userService.getClass().getInterfaces(), new InvocationHandler() {
//参数1proxy、代理对象
//参数2method、要执行方法的描述对象
//参数3arg2、方法的实际参数
public Object invoke(Object proxy, Method method, Object[] arg2) throws Throwable {
myAspect.before();
Object obj = method.invoke(userService, arg2);
myAspect.after();
return obj;
}
});
return proxyService;
}
}

2.2.4、测试类:

1
2
3
4
5
6
7
@Test
public void fun() {
UserService userService = MyBeanFactory.createService();
userService.addUser();
userService.updateUser();
userService.deleteUser();
}

2.3、cglib 代理放式

cglib 代理适用于没有接口只有实现类的情况。cglib 是通过让代理类继承目标类,从而增强目标类的方法。

2.3.1、目标类等同于 jdk 动态代理的接口的实现类

2.3.2、切面类同上

2.3.3、测试类同上

2.3.4、代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MyBeanFactory {
public static UserServiceImpl createService() {
// 1、目标类
final UserServiceImpl userService = new UserServiceImpl();
// 2、切面类
final MyAspect myAspect = new MyAspect();
// 3、代理类
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(userService.getClass());
/* 设置回调函数 , MethodInterceptor接口 等效 jdk中 InvocationHandler接口
* intercept() 等效 jdk invoke()
*/
enhancer.setCallback(new MethodInterceptor() {
//参数1、代理对象
//参数2、要执行方法的描述对象(当前)
//参数3、方法的实际参数
//参数4、代理类方法的代理
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
myAspect.before();
//通过反射的方式执行要目标类的方法
Object obj = arg1.invoke(userService, arg2);
//执行代理类的父类(目标类)
arg3.invokeSuper(arg0, arg2);
myAspect.after();
return obj;
}
});
//创建代理对象,向上转型
UserServiceImpl proxyService = (UserServiceImpl) enhancer.create();
return proxyService;
}
}

2.4、AOP 半自动代理

半自动代理采用 spring 提供的 org.springframework.aop.framework.ProxyFactoryBean 类(一个代理工厂)生成代理对象

2.4.1、目标类与 jdk 动态代理相同

2.4.2、切面类:环绕通知(此处与其他不同)

1
2
3
4
5
6
7
8
public class MyAspect implements MethodInterceptor
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("before");
Object obj = arg0.proceed();
System.out.println("after");
return obj;
}
}

2.4.3、配置文件 beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 目标类 -->
<bean id="userService" class="beanfactory.UserServiceImpl"></bean>
<!-- 切面类 -->
<bean id="myAspect" class="beanfactory.MyAspect"></bean>
<!-- 代理类 -->
<!-- 使用spring提供的代理工厂 -->
<bean id="proxyService" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 目标类的接口 -->
<property name="interfaces" value="beanfactory.UserService"></property>
<!-- 目标类 -->
<property name="target" ref="userService"></property>
<!-- 只能放一个切面类 -->
<!-- 切面类的名称 -->
<property name="interceptorNames" value="myAspect"></property>
</bean>
</beans>

2.4.4、测试类

1
2
3
4
5
6
7
8
9
10
@Test
public void fun() {
String xmlPath = "beanfactory/beans.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
// 获得代理对象
UserService userService = (UserService) applicationContext.getBean("proxyService");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MyAspect {
//前置通知,在目标类方法执行前执行
public void before(JoinPoint joinPoint) {
System.out.println("before");
}
//返回通知,在目标类方法执行后执行,可以拿到目标类方法的返回值,如果没有就是null
public void afterreturning(JoinPoint joinPoint, Object ret) {
System.out.println("afterreturning" + ret);
}
//异常通知,当目标类方法抛出异常时执行,可以拿到异常信息,类似于在catch块里面的方法
public void throwable(JoinPoint joinPoint, Throwable e) {
System.out.println("throwable" + " " + e.getMessage());
}
//在最后执行
public void afterafter(JoinPoint joinPoint) {
System.out.println("afterafter");
}
//在方法前后都会执行
public Object invoke(ProceedingJoinPoint jontPoint) throws Throwable {
System.out.println("环绕通知-------->前");
Object obj = jontPoint.proceed();
System.out.println("环绕通知---------->后");
return obj;
}
}

2.5.4、配置文件 beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标类 -->
<bean id="userService" class="aop.UserServiceImpl"></bean>
<!-- 切面类 -->
<bean id="myAspect" class="aop.MyAspect"></bean>
<!-- proxs-target-class="true":强制采用cglib方式代理 -->
<aop:config proxy-target-class="true">
<!-- 切入点 -->
<aop:pointcut expression="execution(* aop.UserService.*(..))" id="myPointCut" />
<!-- 切面 -->
<aop:aspect ref="myAspect">
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="myPointCut" />
<!-- 返回通知 -->
<aop:after-returning method="afterreturning" returning="ret" pointcut-ref="myPointCut" />
<!-- 环绕通知 -->
<aop:around method="invoke" pointcut-ref="myPointCut" />
<!-- 异常通知 -->
<aop:after-throwing method="throwable" throwing="e" pointcut-ref="myPointCut" />
<!-- 最终通知 -->
<aop:after method="afterafter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>

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
2
3
4
5
public class MyAspectBefore implements MethodBeforeAdvice {
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("before");
}
}

环绕通知:

1
2
3
4
5
6
7
8
public class MyAspectMethod implements MethodInterceptor {
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("环绕前");
Object obj = arg0.proceed();
System.out.println("环绕后");
return obj;
}
}

配置方式:

1
2
3
4
5
6
7
<aop:config proxy-target-class="true">
<!-- 切入点 -->
<aop:pointcut expression="execution(* aop2.UserService.*(..))" id="myPointCut" />
<!-- 特殊的切面,只有一个环绕通知 -->
<aop:advisor advice-ref="myAspectMethod" pointcut-ref="myPointCut" />
<aop:advisor advice-ref="myAspectBefore" pointcut-ref="myPointCut" />
</aop:config>

集合整理及基础操作

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
2
3
4
5
6
7
Map m = new HashMap();m.put("1", "1");
m.put("2", "2");
m.put("3", "3");
m.put("4", "4");
Map m1 = new HashMap();
m1.put("a","a");
m.putAll(m1);

使用 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、源码和原理

HashMap 原理与源码解析

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
2
3
4
5
6
7
Map<Integer,Integer> m2 = new TreeMap(new Comparator() {    @Override
public int compare(Object o1, Object o2) {
Double a = (Double) o1;
Double b = (Double) o2;
return (a > b ? 1 : 0);
}
});

2.2.3、源码和原理

TreeMap 原理与源码解析

3、list 介绍

list 继承了 collection 接口,是 ArrayList,LinkedList 等 List 集合的跟接口。list 集合是单列集合。
因为 list 底层采用链表或数组实现,所以 list 是一种无序的集合。

3.1、ArrayList

3.1.1、增删改等操作

采用 add()方法添加元素添加为 list 的最后一个元素,也可以将元素添加到指定位置。添加指定位置时,按照索引地址添加,从 0 开始。
采用 addAll()方法可以将另一个集合(实现了 Collection)整体添加进来。也可以添加到指定位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
List l1 = new ArrayList();
l1.add("1");
l1.add(1,"2");
l1.add("3");
l1.add("4");
List l2 = new ArrayList();
l2.add("5");
l2.add("6");
List l3 = new ArrayList();
l3.add("7");
l3.add("8");
l1.addAll(l2);
l1.addAll(1,l3);

使用 get()方法根据元素的下标获取元素的值。

1
Object o = l1.get(1);

使用 clear()方法可以清楚集合中的元素。

1
l1.clear();

contains()方法和 containsAll()方法用于判断该集合中是否包含指定的元素,或集合。

1
2
boolean contains = l1.contains("1");
boolean b = l1.containsAll(l3);

isEmpty()方法用于判断该集合是否为空。

1
boolean empty = l1.isEmpty();

使用 remove()方法可以移除集合中的元素,可以直接移除也可以根据索引移除。

1
2
l1.remove(3);
l1.remove("7");

使用 set()方法可以为集合中指定位置的元素设置值。

1
l1.set(1,"a");

list 虽然无序,但是可以使用 sort()方法对其排序,需要传入自定义的比较器。会按照比较器规则排序。

1
2
3
4
5
l1.sort(new Comparator() {    @Override
public int compare(Object o1, Object o2) {
return 0;
}
});

3.1.2、源码和原理

ArrayList 原理与源码解析

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
2
3
4
5
6
l1.addFirst("1");
l1.addLast("2");
l1.removeFirst();
l1.removeLast();
l1.getFirst();
l1.getLast();

使用 offer(),offerLast(),offerFirst()方法,添加元素,可以获取返回值绝对为 true。

1
2
3
l1.offer("1");
l1.offerFirst("2");
l1.offerLast("3");

使用 peek(),peekLast(),peekFirst()获取元素头节点尾节点的值,不会移除节点。

1
2
3
Object peek = l1.peek();
Object o1 = l1.peekFirst();
Object o = l1.peekLast();

使用 poll(),pollLast(),pollFirst()获取元素头节点尾节点的值,会移除节点。

1
2
3
Object poll = l1.poll();
Object o2 = l1.pollFirst();
Object o3 = l1.pollLast();

3.2.3、源码和原理

LinkedList 原理与源码解析

4、Set 介绍

4.1、HashSet

4.1.1、增删改等操作

使用 add()方法添加元素。

1
2
3
4
HashSet set = new HashSet();set.add("1");
set.add("2");
set.add("3");
set.add("4");

使用 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、源码和原理

HashSet 原理与源码分析

4.2、TreeSet

4.2.1、与 HashSet 区别

与 HashSet 相似,TreeSet 底层同样采用 TreeMap 实现。同样具备 HashSet 不具备的排序能力。
底层使用 TreeMap 存放 key 的一列,而基于红黑树实现。

4.2.2、增删改等操作

TreeSet 实现了与 HashSet 相同的 Set 接口,其基础操作相同。
因为其具备排序能力,所以在 TreeSet 存储自定义对象时,自定义对象需要实现 Comparator 接口,重写其中的方法。
需要自定义排序规则。

1
2
3
4
TreeSet t = new TreeSet(new Comparator() {
@Override public int compare(Object o1, Object o2) {
return 0; }
});

4.2.3、源码和原理

TreeSet 原理与源码分析

集合并发修改异常

先看例子
在对集合进行迭代器遍历时,在遍历过程中对集合中元素进行增加或删除,在删除数据之后会报并发修改异常。

原因:

在迭代过程中,不能修改集合结构。
我们采用 hasnext()方法判断集合是否还有元素可遍历,采用 next()方法获取元素。而 next()方法会每次检查集合结构是否发生变化。

1
2
3
4
5
6
7
8
9
10
11
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}

next()方法会在第一行调用 checkForComodification()方法检测集合结构是否发生变化。如果发生变化则会报异常。

1
2
3
4
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

checkForComodification()方法会检查集合结构是否发生变化。
cursor 是下一次要取的元素下标,next()方法执行后,cursor 就会往后移动一下。
这里modCount是 ArrayList 继承自 AbstractList 的变量,用来记录集合元素个数发生变化(增加和删除)的次数。
expectedModCount是类 Itr 的变量,是迭代器记录集合元素个数变化的次数。如下图所示:

1
2
3
4
5
6
7
8
9
10
11
12
public Iterator<E> iterator() {
return new Itr();
}

/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}

在调用 iterator()方法时会返回一个 Itr 类的对象,同时会创建变量 expectedModCount,并将 modCount 传递给它,此时这两个值相等。

1
2
3
4
5
6
7
8
9
10
11
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
1
2
3
4
5
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
1
2
3
4
5
6
7
8
9
10
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

而我们在对集合中添加或删除元素时,都会改变 modCount 的值,如上图所示:
当添加或删除这个操作在迭代过程中时,next()方法检查到 modCount 与 expectedModCount 值不一样,就会报并发修改异常。
那为什么迭代器自己的方法不会报异常?如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

我们发现迭代器自己的 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
2
3
public boolean hasNext() {
return cursor != SubList.this.size;
}

程序继续执行,hasnext()方法判断 cursor 和 size 相同,会返回 false,集合遍历将会终止。
2、在 foreach 循环遍历集合时中也不能采用集合的增加删除方法。因为 foreach 循环底层也是通过迭代器进行遍历,如果修改,同样会报异常。
3、
**

缓存穿透,击穿,雪崩

1、缓存穿透

概念:

正常情况下,需要查询的数据都存在,当查询一个缓存和数据库都不存在的数据时,每次请求都会落在数据库里,这种情况成称为缓存穿透。

问题:

缓存穿透一般会导致数据库压力增大。恶意攻击会击垮数据库。会绕过缓存。

解决:

1、在接口增加参数校验,不合法的直接返回。
2、缓存空值,将对应 key 的 value 设置为空值,避免暴力攻击。同时将 key 失效时间设置短一些,避免影响正常使用。
3、在网关阈值,限制同 ip 访问量。
4、高级用户布隆过滤器。bloom filter,可以对 key 进行判断是否在数据库存在,不存在就直接返回,存在就查询出来,并刷新缓存。

2、缓存击穿

概念:

高并发系统中,大量请求一般会落在缓存中,但在某一时刻这个热点 key 过期了,此刻大量请求就会落在数据库。

问题:

会导致数据库压力增大,严重者击垮数据库。

解决:

1、设置热点 key 不过期。
2、加上分布式锁,每次只有拿到锁的线程可以去访问数据库。第一个线程查询到后就会缓存起来,后面线程从缓存中拿。

3、缓存雪崩

概念:

某一时刻发生大规模缓存不可用问题,比如宕机,过期。

问题:

轻则查询变慢,重则大面积服务不可用。

解决:

1、采用分布式集群,减少宕机风险。
2、将 key 的失效时间设置为随机数,避免大量缓存同时失效。
3、采用本地缓存加限流逻辑。

多线程间通信

1、案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.company;
class NumberHolder {
private int number;
public synchronized void increase() {
if (0 != number) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 能执行到这里说明已经被唤醒
// 并且number为0
number++;
System.out.print(number+"-");
// 通知在等待的线程
notify();
}
public synchronized void decrease() {
if (0 == number) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 能执行到这里说明已经被唤醒
// 并且number不为0
number--;
System.out.print(number+"-");
notify();
}
}
class IncreaseThread extends Thread {
private NumberHolder numberHolder;
public IncreaseThread(NumberHolder numberHolder) {
this.numberHolder = numberHolder;
}
@Override
public void run() {
for (int i = 0; i < 20; ++i) {
// 进行一定的延时
try {
Thread.sleep((long) Math.random() * 10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 进行增加操作
numberHolder.increase();
}
}
}
class DecreaseThread extends Thread {
private NumberHolder numberHolder;
public DecreaseThread(NumberHolder numberHolder) {
this.numberHolder = numberHolder;
}
@Override
public void run() {
for (int i = 0; i < 20; ++i) {
// 进行一定的延时
try {
Thread.sleep((long) Math.random() * 10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 进行减少操作
numberHolder.decrease();
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
NumberHolder numberHolder = new NumberHolder();
Thread t1 = new IncreaseThread(numberHolder);
Thread t2 = new DecreaseThread(numberHolder);
// Thread t3 = new DecreaseThread(numberHolder);
// Thread t4 = new DecreaseThread(numberHolder);
t1.start();
t2.start();
// t3.start();
// t4.start();
}
}

2、分析

此案例

多线程创建

1、线程的创建

1.1、继承 Tread 类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class myThread {
public static void main(String[] args) {
myThread1 m1 = new myThread1("你好1");
m1.start();
myThread1 m2 = new myThread1("你好2");
m2.start();
}
}
//继承Thread类
class myThread1 extends Thread {
private int tickets = 100;
private String name;
public myThread1(String name) {
this.name = name;
}
//doSomeThing
@Override
public void run() {
while (this.tickets > 0) {
System.out.println(this.name + "--------" + this.tickets--);
}
}
}

1.2、实现 Runnable 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class myThreadtwo {
private static int aa = 10;
public static void main(String[] args) {
mythread1 m1 = new mythread1(aa);
Thread t1 = new Thread(m1,"第一个");
Thread t2 = new Thread(m1,"第二个");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(aa);
}
});
t1.start();
t2.start();
t3.start();
}
}
//实现Runnable类
class mythread1 implements Runnable {
private int tickets = 10;
public mythread1(int t) {
this.tickets = t;
}
//doSomeThing
@Override
public void run() {
// synchronized (this) {
while (this.tickets > 0) {
System.out.println(Thread.currentThread().getName() + "--------" + this.tickets--);
}
System.out.println(this);
// }
}
}

1.3、实现 Callable 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class myThreadThree {
private static int aa = 10;
public static void main(String[] args) {
myThreadnew m1 = new myThreadnew(aa);
FutureTask future = new FutureTask(m1);
Thread t1 = new Thread(future,"第一个");
Thread t2 = new Thread(future,"第二个");
Thread t3 = new Thread(new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
return null;
}
}));
t1.start();
t2.start();
t3.start();
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//实现Callable类
class myThreadnew implements Callable{
private int tickets = 10;
public myThreadnew(int t) {
this.tickets = t;
}
//doSomeThing
@Override
public Object call() throws Exception {
while (this.tickets > 0) {
System.out.println(Thread.currentThread().getName() + "--------" + this.tickets--);
}
System.out.println(this);
return null;
}
}

2、创建方式的比较

如果一个类继承  Thread,则不适合资源共享。但是如果实现了  Runable  接口的话,则很容易的实现资源共享。
相对来说,实现 Runnable 接口比继承 Thread 好。个人感觉完成同一个任务实现接口比较方便,跑不同的任务,继承 Thread 比较方便。
采用匿名内部类使用 Runnable 接口,会隐式的印用当前 Activity,会造成内存泄露。

1、实现 Runnable 的优势

可以避免 java 中的单继承的限制
线程池只能放入实现  Runable  或  callable  类线程,不能直接放入继承 Thread  的类

2、两者都有的

适合多个相同的程序代码的线程去处理同一个资源
增加程序的健壮性,代码可以被多个线程共享,代码和数据独立