NanYin的博客

记录生活点滴


  • Home

  • About

  • Tags

  • Categories

  • Archives

  • Search

设计模式之责任链模式

Posted on 2019-05-27 | In 设计模式
Words count in article: 1.1k | Reading time ≈ 4

设计模式之责任链模式

责任链模式是一种对象的行为模式.通过为多个对象处理客户端请求的方式,实现降低发送者与接收者之间的耦合性.对象链由一个对象对下一个对象的引用组成一条链,请求在这个链上传递,直到这个链上的某一个对象处理它.

...

King只需要下达命令,具体谁来做并不关心,然而这个命令会通过commander, officer,soldier,这三类人只需要来执行各自负责的部分,如果没有自己负责的那么就给下一个职责的人就行了.这就是责任链模式.下面根据例子类图来分析责任链中的结构关系.

责任链的结构

责任链的基本机构由下面三部分组成:

  • 抽象处理者 RequestHandler,定义接口方法,使用handler方法规定实现类通过该方法来规范子类的实现.
  • 具体处理者 通过继承RequestHandler实现其中的handle方法
  • 请求Request,客户端通过方法将request放入处理者链中,处理者一次执行这个request.request在构成的责任链上进行传递,具体处理人根据条件判断是否需要处理,如果不需要处理,流转至下一个人.

正常的责任链模式请求总是会被处理的,并且请求不会被多次处理.

在日常工作时,经常会遇到请假审批的情况.现在构建一个场景,当一个人在项目中,那么就让项目经理审批通过即可,否则需要他对应部门的主管审批通过.如张三发起请求,请求先到项目经理处,发现不属于项目成员,则再提交至张三的部门主管处,进行审批.

责任链模式的使用环境

1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
3、可动态指定一组对象处理请求

场景实现

通过类图可以看到继承和调用关系:

责任链类图

下面看具体实现:

  1. 抽象处理者,定义一个LeaveHandler,作为请假处理人的抽象类
1
2
3
4
5
6
7
8
9
10
public abstract class LeaveHandler {
// 设置下一个处理人,通过next构成责任链,链上就是处理人
public LeaveHandler next;
// 构造函数
public LeaveHandler(LeaveHandler next) {
this.next = next;
}
// 抽象方法,处理请假方法
public abstract LeaveHandler handle(LeaveRequest request);
}
  1. 具体处理者,定义两个具体处理人,分别为部门主管的 LeaderHandler 和 项目主管的 ManagerHandler 都继承自抽象类
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
public class ManagerHandler extends LeaveHandler {
private static final Logger logger = LoggerFactory.getLogger(ManagerHandler.class);
public ManagerHandler(LeaveHandler next) {super(next);}
@Override
public LeaveHandler handle(LeaveRequest request) {
// 如果人员不再项目中,流转到下一个人,如果在项目中,直接审批通过
if(request.isInProject()){
logger.info("项目经理对{}在{}请假审批通过!!",request.getName(),request.getDate());
}else{
logger.info("{} 不在项目人员中,流转到下一审批人!!",request.getName());
// 流转到下一人
next.handle(request);
}
return this;
}
}

public class LeaderHandler extends LeaveHandler {
private static final Logger logger = LoggerFactory.getLogger(LeaderHandler.class);
public LeaderHandler(LeaveHandler next) {super(next);}
@Override
public LeaveHandler handle(LeaveRequest request) {
logger.info("部门主管对{}在{}请假审批通过!!",request.getName(),request.getDate());
return this;
}
}
  1. 请求Request,定义一个请假请求LeaveRequest,包含请假人姓名和请求时间
1
2
3
4
5
6
7
8
9
10
11
public class LeaveRequest {
private String name;
private String date;
private boolean inProject;
// .... 省略get set
public LeaveRequest(String name, String date, boolean inProject) {
this.name = name;
this.date = date;
this.inProject = inProject;
}
}
  1. client 调用者执行请假的流程
1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) {
// 添加请求
LeaveRequest request = new LeaveRequest("zhangsan","0919",true);
LeaveRequest request2 = new LeaveRequest("lisi","0929",false);
// 设置责任链
LeaveHandler manageHandler = new ManagerHandler(new LeaderHandler(null));
// 责任链处理
manageHandler.handle(request).handle(request2);
}
}

最后的执行结果:

1
2
3
项目经理对zhangsan在0919请假审批通过!!
lisi 不在项目人员中,流转到下一审批人!!
部门主管对lisi在0929请假审批通过!!

设计模式之策略模式

Posted on 2019-05-27 | In 设计模式
Words count in article: 620 | Reading time ≈ 2

java设计模式之策略模式

如果存在一种行为在多种情况下的实现,一般情况下可能会使用if...else if...else..来实现。但是每增加一种情况就要增加一种if判断,这样不符合java设计的规范,这时,就可以使用策略模式来替换这种繁琐的if..else

策略设计模式是行为设计模式的一种.当使用多种算法(条件)解决一个特殊的任务的时候,由客户端决定使用哪种具体算法进行特定的实现时,可以使用策略模式.

哪种情况会使用策略模式

以下情况可以使用策略模式:

  1. 许多相关类只有行为上有所不同,策略模式可以通过相同的行为方法(接口方法)来配置这些类.
  2. 当需要使用到算法的变种的时候,当定义不同的行为参数,如果使用类的层次结构实现的时候,可以使用策略模式(当使用方法层次结构的时候就是多态)
  3. 客户端不用清楚算法的细节实现,只需关注接口.避免暴露具体的类的实现、特殊算法的数据结构.
  4. 一个类定义了多种不同的行为,并且使用杂乱的条件进行判断具体使用哪一个条件,这时候就可以使用策略模式来代替,对不同的策略形成不同的类.可以清晰的表达出类的作用与实现的行为方式.

策略模式结构

策略模式类图

如上图定义一个总的Traffic接口,里面有一个go()方法.使用car和walk两种策略(算法)分别来实现接口.

Context类是环境类,持有一个TrafficStrategy的引用,隐藏具体的策略接口的实现,让客户端专注于方法的调用.

代码实现

策略模式接口和策略的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface TrafficStrategy {
void go();
}
public class WalkStrategy implements TrafficStrategy{
@Override
public void go() {
System.out.println("i am walking");
}
}
public class CarStrategy implements TrafficStrategy {

@Override
public void go() {
System.out.println("i am driving");
}
}

context类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Context {
private TrafficStrategy trafficStrategy;

public Context(TrafficStrategy trafficStrategy) {
this.trafficStrategy = trafficStrategy;
}

public TrafficStrategy getTrafficStrategy() {
return trafficStrategy;
}

public void setTrafficStrategy(TrafficStrategy trafficStrategy) {
this.trafficStrategy = trafficStrategy;
}

public void doStrategy(){
trafficStrategy.go();
}
}

App客户端类:

1
2
3
4
5
6
public class App {
public static void main(String[] args) {
Context context = new Context(new CarStrategy());
context.doStrategy();
}
}

Spring重点知识学习

Posted on 2019-05-05 | In Spring
Words count in article: 2.3k | Reading time ≈ 8

Spring Framework

使用spring可以轻易的创建一个企业级应用,它使用Java语言所有的企业级环境中的所有东西,spring是开源的,拥有庞大而活跃的社区来提供持续的支持.

Spring Core

spring分为多个模块,其中core为其中的一个,里面包含spring的核心技术点

IOC容器(控制反转)

IOC也被成为依赖注入,这是由一个对象定义他所依赖的关系的过程.当创建这个bean的时候也同时创建这些依赖供其使用.这样的整个过程就是反转控制的过程,比如说建房子,以前需要砖,瓦,水泥才能建造房子,如果不实用ioc时,就是自己去造砖,造瓦,然后使用.
现在使用ioc容器,只需要把砖,瓦,水泥写到清单上,ioc容器自动把这些准备好供使用,省去来自己制造的过程.

org.springframework.beans 和 org.springframework.context 是spring实现IOC容器的基础.BeanFactory接口是能够管理任何对象的高级的配置接口,ApplicationContext是BeanFactory的子接口.因为:

  • 更容易和Spring 的AOP特性相结合
  • 消息资源处理,用于国际化问题
  • 事件发布
  • 用于特定的上下文比如web应用程序的WebApplicationContext

简单来说就是BeanFactory提供了基本的功能,而ApplicationContext提供了更多的企业定制化的功能.

ApplicationContext代表了IOC容器,提供了初始化、配置、组装beans.通过配置元数据来获取有关初始化..等的指令.这里所指的配置元数据可以通过配置文件,注解或者java代码来实现.通过配置元数据来体现对象和其依赖.

下图为官网提供的spring工作的简化图:

IOC容器

SpringBean的生命周期

生命周期
生命周期大概包含这几个阶段:

  1. 实例化Bean对象,这时还未进行依赖注入,最后将bean加入到beanwapper中.
  2. 设置对象属性.Spring根据BeanDefinition中的信息进行依赖注入.并且通过BeanWrapper提供的设置属性的接口完成依赖注入。
  3. 注入Aware接口,检测是否是想相关Aware接口,并装配相关接口的方法.
  4. BeanPostProcessor 此时bean已经成功被构造,通过实现这个接口来自定义前置和后置的处理规则.
  5. 调用initialzingBean和int-methods,这时对象成功被初始化
  6. 最后调用disposableBean的afterpropertiesSet方法和destroy-methods来实现指定对象销毁.

Spring AOP

AOP的意思是面向切面编程,在理解面向切面前,不得不说面向对象.什么是面向对象,面向对象编程就是将事务对象化,依据对象进行层级化,面向对象通过封装继承多态的方式实现了对象层次结构,但是实际应用时可能会遇到需要横向的进行业务处理,典型的就如日志,因为在实际代码编写时,不能每次在写方法前后都加入相同的代码,这样即浪费时间,又导致代码冗余.所以面向切面编程必不可少.

AOP将多个相同的业务代码整合,形成一个通用的“切面”,通过切面可以分割出“核心关注点”和“横切关注点”,核心关注点就是实际的核心业务关注的代码逻辑部分,横切关注点就是公共的代码的实现.

其中AOP中有几个比较关键的概念:

  1. 横切关注点:对哪些方法进行拦截,拦截后如何处理.这些关注点叫做横切关注点
  2. 切面:切面就是对横切关注点的抽象,就如同类对事物的抽象类似
  3. 连接点(joinpoint):被拦截到的点,可以通过连接点获取被拦截的参数等信息
  4. 切入点:连接点进行拦截的定义
  5. 织入:横切的过程叫做织入

简单的代码实现(注解)

下面代码使用注解的方式实现的Spring AOP,配置文件同理,需要配置切面(主要是切点和通知方法)

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
@Component
// 标志为AOP @Aspect
public class ServiceLog {
private Logger logger = LoggerFactory.getLogger(ServiceLog.class);

@Autowired
HttpSession httpSession;

// 前置通知 切入点前执行
@Before("execution(* com.nanyin.jreact.service.serviceImpl..*.*(..))")
public void handelServiceMethodBefore(JoinPoint joinpoint){ //JoinPoint 为连接点,通过连接点可获取参数等
try{
String username = (String) httpSession.getAttribute("user");
BaseUtil.setUser(Optional.ofNullable(username).orElse("空 "));
}catch (Exception e){
e.printStackTrace();
}
}
// 后置通知 切入点后执行
@After("execution(* com.nanyin.jreact.service.serviceImpl..*.*(..))")
public void handelServiceMethodAfter(JoinPoint joinpoint){
try{
String username = (String) httpSession.getAttribute("user");
BaseUtil.setUser(Optional.ofNullable(username).orElse("空 "));
}catch (Exception e){
e.printStackTrace();
}
}
}

JDK动态代理和CGLIB代理

两者都可以实现AOP代理功能,但是jdk代理需要代理类和委托类都实现系统的接口,但是实际应用的时候这种情况非常少,这时候会自动使用CGLIB实现动态代理

Spring事务管理

事务包含四个基本特性,也就是常说的ACID,分别为原子性,一致性,隔离性,持久性

事务的接口

  1. PlatformTransactionManager: (平台)事务管理器

spring 不直接管理事务,而是实现不同的事务管理器,将事务交给框架处理,如为ibaits 和 hibernate 有不同的实现.

1
2
3
4
5
6
<!-- 事务管理器 xml 配置 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
  1. TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
1
2
3
4
5
6
7
8
9
10
11
12
public interface TransactionDefinition {
// 返回事务的传播行为
int getPropagationBehavior();
// 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getIsolationLevel();
//返回事务的名字
String getName();
// 返回事务必须在多少秒内完成
int getTimeout();
// 返回是否优化为只读事务。
boolean isReadOnly();
}
  1. TransactionStatus: 用来记录事务运行状态
1
2
3
4
5
6
7
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事物
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}

自定义注解

构造注解

注解使用@interface来标识为一个注解,具体使用可以参考如下:

1
2
3
4
5
6
7
8
9
10
11
// @Target原注解的意义是表明作用域,比如type就是表示要标注到类接口上
@Target({ElementType.TYPE})
// 表示作用域
@Retention(RetentionPolicy.RUNTIME)
// 表明其他注解能够继承该注解
@Inherited
// javadoc会包含
@Documented
public @interface TableDescription {
String description();
}

其中该注解的作用就是1.标注在类接口上2.作用在运行时,能够通过反射得到接口中的内容3.能够让其他注解继承该注解4.能够生成javaddoc文档

下面来重点关注第一个和第二个元注解:

  1. @Target是这个注解的作用域,ElementType.METHOD是这个注解的作用域的列表,METHOD是方法声明,除此之外,还有:CONSTRUCTOR(构造方法声明),FIELD(字段声明),LOCAL VARIABLE(局部变量声明),METHOD(方法声明),PACKAGE(包声明),PARAMETER(参数声明),TYPE(类接口)
  2. @Retention是它的生命周期,前面不是说注解按照运行机制有一个分类嘛,RUNTIME就是在运行时存在,可以通过反射读取。除此之外,还有: SOURCE(只在源码显示,编译时丢弃),CLASS(编译时记录到class中,运行时忽略),RUNTIME(运行时存在,可以通过反射读取)

如何使用自定义注解

  • 添加注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Data
@Table(name = "person")
// 这里使用到了上面的自定义注解TableDescription
@TableDescription(description = "this is person table")
public class Person {
@Id
private long id;
@Column(name = "name",length = 128)
private String name;

@Column(name = "age",length = 3)
private short age;
}
  • 获得注解内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void testAnn(){
try{
// 反射获取类
Class clazz = Class.forName("com.nanyin.jreact.entry.Person");
Boolean exist = clazz.isAnnotationPresent(TableDescription.class);
if(exist){
//如果注解在类中存在
TableDescription tableDescription = (TableDescription) clazz.getAnnotation(TableDescription.class);
// 获得注解内容
System.out.println(tableDescription.description());
}
}catch (ClassNotFoundException c){

}
}

上面只是简单的掩饰注解的用法,但实际上业务会比这难上许多.

springMVC 工作流程

先上一张图:来自https://www.cnblogs.com/xiaoxi/p/6164383.html
SpringMvc工作原理

由图中可以看到SpringMvc中最重要的起到中枢的是DispatcherServlet(前端控制器).

  1. 在DispatcherServlet接受到客户端请求后,使用 handlerMapping 进行映射处理.在生成处理器映射器之后, 返回一个handler,如果有拦截器的化还生成拦截器.交给 DispatcherServlet 进行下一步处理.
  2. 第二步交给 handlerAdatper (处理器适器) HandlerAdapter 经过适配调用具体的处理器( Controller ,也叫后端控制器)。Controller执行完成返回ModelAndView。
  3. 第三步,DispatcherServlet将最终的结果交给 viewResolver 试图解析器进行视图解析
  4. DispatcherServlet 根据返回的view进行视图渲染,最终返回给客户端用户.

Spring重点知识学习

Posted on 2019-05-05
Words count in article: 2.3k | Reading time ≈ 8

Spring Framework

使用spring可以轻易的创建一个企业级应用,它使用Java语言所有的企业级环境中的所有东西,spring是开源的,拥有庞大而活跃的社区来提供持续的支持.

Spring Core

spring分为多个模块,其中core为其中的一个,里面包含spring的核心技术点

IOC容器(控制反转)

IOC也被成为依赖注入,这是由一个对象定义他所依赖的关系的过程.当创建这个bean的时候也同时创建这些依赖供其使用.这样的整个过程就是反转控制的过程,比如说建房子,以前需要砖,瓦,水泥才能建造房子,如果不实用ioc时,就是自己去造砖,造瓦,然后使用.
现在使用ioc容器,只需要把砖,瓦,水泥写到清单上,ioc容器自动把这些准备好供使用,省去来自己制造的过程.

org.springframework.beans 和 org.springframework.context 是spring实现IOC容器的基础.BeanFactory接口是能够管理任何对象的高级的配置接口,ApplicationContext是BeanFactory的子接口.因为:

  • 更容易和Spring 的AOP特性相结合
  • 消息资源处理,用于国际化问题
  • 事件发布
  • 用于特定的上下文比如web应用程序的WebApplicationContext

简单来说就是BeanFactory提供了基本的功能,而ApplicationContext提供了更多的企业定制化的功能.

ApplicationContext代表了IOC容器,提供了初始化、配置、组装beans.通过配置元数据来获取有关初始化..等的指令.这里所指的配置元数据可以通过配置文件,注解或者java代码来实现.通过配置元数据来体现对象和其依赖.

下图为官网提供的spring工作的简化图:

IOC容器

SpringBean的生命周期

生命周期
生命周期大概包含这几个阶段:

  1. 实例化Bean对象,这时还未进行依赖注入,最后将bean加入到beanwapper中.
  2. 设置对象属性.Spring根据BeanDefinition中的信息进行依赖注入.并且通过BeanWrapper提供的设置属性的接口完成依赖注入。
  3. 注入Aware接口,检测是否是想相关Aware接口,并装配相关接口的方法.
  4. BeanPostProcessor 此时bean已经成功被构造,通过实现这个接口来自定义前置和后置的处理规则.
  5. 调用initialzingBean和int-methods,这时对象成功被初始化
  6. 最后调用disposableBean的afterpropertiesSet方法和destroy-methods来实现指定对象销毁.

Spring AOP

AOP的意思是面向切面编程,在理解面向切面前,不得不说面向对象.什么是面向对象,面向对象编程就是将事务对象化,依据对象进行层级化,面向对象通过封装继承多态的方式实现了对象层次结构,但是实际应用时可能会遇到需要横向的进行业务处理,典型的就如日志,因为在实际代码编写时,不能每次在写方法前后都加入相同的代码,这样即浪费时间,又导致代码冗余.所以面向切面编程必不可少.

AOP将多个相同的业务代码整合,形成一个通用的“切面”,通过切面可以分割出“核心关注点”和“横切关注点”,核心关注点就是实际的核心业务关注的代码逻辑部分,横切关注点就是公共的代码的实现.

其中AOP中有几个比较关键的概念:

  1. 横切关注点:对哪些方法进行拦截,拦截后如何处理.这些关注点叫做横切关注点
  2. 切面:切面就是对横切关注点的抽象,就如同类对事物的抽象类似
  3. 连接点(joinpoint):被拦截到的点,可以通过连接点获取被拦截的参数等信息
  4. 切入点:连接点进行拦截的定义
  5. 织入:横切的过程叫做织入

简单的代码实现(注解)

下面代码使用注解的方式实现的Spring AOP,配置文件同理,需要配置切面(主要是切点和通知方法)

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
@Component
// 标志为AOP @Aspect
public class ServiceLog {
private Logger logger = LoggerFactory.getLogger(ServiceLog.class);

@Autowired
HttpSession httpSession;

// 前置通知 切入点前执行
@Before("execution(* com.nanyin.jreact.service.serviceImpl..*.*(..))")
public void handelServiceMethodBefore(JoinPoint joinpoint){ //JoinPoint 为连接点,通过连接点可获取参数等
try{
String username = (String) httpSession.getAttribute("user");
BaseUtil.setUser(Optional.ofNullable(username).orElse("空 "));
}catch (Exception e){
e.printStackTrace();
}
}
// 后置通知 切入点后执行
@After("execution(* com.nanyin.jreact.service.serviceImpl..*.*(..))")
public void handelServiceMethodAfter(JoinPoint joinpoint){
try{
String username = (String) httpSession.getAttribute("user");
BaseUtil.setUser(Optional.ofNullable(username).orElse("空 "));
}catch (Exception e){
e.printStackTrace();
}
}
}

JDK动态代理和CGLIB代理

两者都可以实现AOP代理功能,但是jdk代理需要代理类和委托类都实现系统的接口,但是实际应用的时候这种情况非常少,这时候会自动使用CGLIB实现动态代理

Spring事务管理

事务包含四个基本特性,也就是常说的ACID,分别为原子性,一致性,隔离性,持久性

事务的接口

  1. PlatformTransactionManager: (平台)事务管理器

spring 不直接管理事务,而是实现不同的事务管理器,将事务交给框架处理,如为ibaits 和 hibernate 有不同的实现.

1
2
3
4
5
6
<!-- 事务管理器 xml 配置 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
  1. TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
1
2
3
4
5
6
7
8
9
10
11
12
public interface TransactionDefinition {
// 返回事务的传播行为
int getPropagationBehavior();
// 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getIsolationLevel();
//返回事务的名字
String getName();
// 返回事务必须在多少秒内完成
int getTimeout();
// 返回是否优化为只读事务。
boolean isReadOnly();
}
  1. TransactionStatus: 用来记录事务运行状态
1
2
3
4
5
6
7
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事物
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}

自定义注解

构造注解

注解使用@interface来标识为一个注解,具体使用可以参考如下:

1
2
3
4
5
6
7
8
9
10
11
// @Target原注解的意义是表明作用域,比如type就是表示要标注到类接口上
@Target({ElementType.TYPE})
// 表示作用域
@Retention(RetentionPolicy.RUNTIME)
// 表明其他注解能够继承该注解
@Inherited
// javadoc会包含
@Documented
public @interface TableDescription {
String description();
}

其中该注解的作用就是1.标注在类接口上2.作用在运行时,能够通过反射得到接口中的内容3.能够让其他注解继承该注解4.能够生成javaddoc文档

下面来重点关注第一个和第二个元注解:

  1. @Target是这个注解的作用域,ElementType.METHOD是这个注解的作用域的列表,METHOD是方法声明,除此之外,还有:CONSTRUCTOR(构造方法声明),FIELD(字段声明),LOCAL VARIABLE(局部变量声明),METHOD(方法声明),PACKAGE(包声明),PARAMETER(参数声明),TYPE(类接口)
  2. @Retention是它的生命周期,前面不是说注解按照运行机制有一个分类嘛,RUNTIME就是在运行时存在,可以通过反射读取。除此之外,还有: SOURCE(只在源码显示,编译时丢弃),CLASS(编译时记录到class中,运行时忽略),RUNTIME(运行时存在,可以通过反射读取)

如何使用自定义注解

  • 添加注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Data
@Table(name = "person")
// 这里使用到了上面的自定义注解TableDescription
@TableDescription(description = "this is person table")
public class Person {
@Id
private long id;
@Column(name = "name",length = 128)
private String name;

@Column(name = "age",length = 3)
private short age;
}
  • 获得注解内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void testAnn(){
try{
// 反射获取类
Class clazz = Class.forName("com.nanyin.jreact.entry.Person");
Boolean exist = clazz.isAnnotationPresent(TableDescription.class);
if(exist){
//如果注解在类中存在
TableDescription tableDescription = (TableDescription) clazz.getAnnotation(TableDescription.class);
// 获得注解内容
System.out.println(tableDescription.description());
}
}catch (ClassNotFoundException c){

}
}

上面只是简单的掩饰注解的用法,但实际上业务会比这难上许多.

springMVC 工作流程

先上一张图:来自https://www.cnblogs.com/xiaoxi/p/6164383.html
SpringMvc工作原理

由图中可以看到SpringMvc中最重要的起到中枢的是DispatcherServlet(前端控制器).

  1. 在DispatcherServlet接受到客户端请求后,使用 handlerMapping 进行映射处理.在生成处理器映射器之后, 返回一个handler,如果有拦截器的化还生成拦截器.交给 DispatcherServlet 进行下一步处理.
  2. 第二步交给 handlerAdatper (处理器适器) HandlerAdapter 经过适配调用具体的处理器( Controller ,也叫后端控制器)。Controller执行完成返回ModelAndView。
  3. 第三步,DispatcherServlet将最终的结果交给 viewResolver 试图解析器进行视图解析
  4. DispatcherServlet 根据返回的view进行视图渲染,最终返回给客户端用户.

Hibernate重点知识总结

Posted on 2019-04-29 | In Hibernate
Words count in article: 1.4k | Reading time ≈ 4

Hibernate重点知识总结

Hibernate的三种状态

  1. 瞬时态(transient):当实体还未和session关联,如new Person(“1”);
  2. 持久态(persistent): 通过get或者load得到的实体类,这时的状态都是持久态.
  3. 游离态(detached): 当通过get或者load得到持久态的对象后,执行删除delete时,这时已经脱离session.因delete而变为游离态可有save或者saveOrUpate转化为持久态.或则当session关闭后,session中的对象就变为游离态.

hibernate中update和saveOrUpdate的区别

update只能是对象有主键时起作用,而saveOrUpdate则在对象没有主键时执行插入操作.对应的update操作只能操作游离态的对象,而不能对瞬时态的对象操作.而saveOrUpdate则两种状态的对象都可以操作.

hibernate中的update和merge方法的区别

当执行update方法的时候,一个session中具有与当前update相同的持久化标识(identifier)的实例,使用upate操作后会报错,而使用merge后把处理自由态的po对象A的属性copy到session当中处于持久态的po的属性中,执行完成后原来是持久状态还是持久态,而我们提供的A还是自由态。

hibernate中get和load的区别

一句话,hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。

hibernate中list和iterator的区别

(1)、List方式是1次性把所有的数据全部取到内存中,构造一个超大的结果集,主要的时间开销是这一步,这一步的时间开销要远远超过JDBC和 Iterator方式下构造结果集的时间开销,并且内存开销也很惊人;而对结果集的遍历操作,速度则是非常的惊人(经过测试,30万记录的内 存遍历不到100ms,由于这一步不受JDBC影响,因此结果可信)。因此,List方式适合于对结果集进行反复多次操作的情况,例如分页显示,往后往前 遍历,跳到第一行,跳到最后一行等等。

(2)、Iterator方式只取记录id到内存中,并没有把所有数据取到内存中,因此构造结果集的时间开销很小,比JDBC和List方式都要少,并且内 存开销也小很多。而对结果集的遍历的操作的时候,Iterator仍然要访问数据库,所有主要的时间开销都花在这里。因此,Iterator方式适合于只 对结果集进行1次遍历操作的情况,并且Iterator方式特别适合于从超大结果集中取少量数据,这种情况Iterator性能非常好。

总结: list把数据一次加载到内存,再进行遍历操作就非常快了.而Iterator方式只会把id放到内存中,但是再次遍历时仍然要访问数据库查询数据,适合一次遍历的情况.

hibernate中的inverse属性

设置inverse=“false”为主控方,由主控方类维护对象间的关系.比如再many-to-one的关系中,再one的一方设置inverse="true"这样能够提高效率,比如班级里一个老师多个学生,老师记住多个学生比较困难,而学生记住一个老师很容易.同理one-to-many.再多对多的关系中,再任意一方设置inverse='true'即可.

hibernate中的cascade属性

级联操作:指当主控方执行某项操作时,是否要对被关联方也执行相同的操作。

什么情况下懒加载能够有效的提高效率

懒加载是当用到对象的时候再从数据库加载数据,持久化来使用.从这句话中就可以理解为,因为及联的关系,一个表的数据可能会带出多张表的数据,这时候使用懒加载就能够实现效率的提高,没必要一次性把所有数据都加载出来,而是用时加载.hibernate默认是使用懒加载的,当然可以使用lazy=false来禁用懒加载.

什么是hibernate的二级缓存

缓存就是将以前的数据存放到内存中,当查询数据的时候,现在内存中查询,是否有这个对象,如果没有再从数据库中查询对象,这样大大的提高了查询的效率.

Hibenate的session就是一级缓存,当查询数据的时候都会先从session中查询对象是否存在,不存在才从数据库查询.但是session是非线程安全的,多线程共享的情况下会出现问题.并且session是表示的是一段会话,表示的是一次事务,基于以上两点,所以在一次事务完成后,session就应立即关闭.所以这里说的一级缓存意义不大.

Hibernate的二级缓存其实就是使用外部缓存,如Echache或者redis等,使用配置文件配置缓存策略等.

Hibernate中使用hibernateTemplate的好处有哪些?

  1. HibernateTemplate简化了与Hibernate Session的交互
  2. 常见的函数简化为单个方法调用。
  3. 异常会自动捕获并转换为运行时异常
  4. session 自动关闭

JVM基本知识整理

Posted on 2019-04-24 | In Java , JVM
Words count in article: 1.7k | Reading time ≈ 5

java内存空间

内存空间

其中如图中所展示的,按功能分了大致分为两类,一类为线程共享的JAVA堆和元数据区和直接内存,另一类是线程私有的虚拟机栈,方法区栈和程序计数器。

下面针对这几种区域来详细将这几个区域分别的作用是什么。

程序计数器

程序计数器是一块较小的内存空间,可以看作是当前线程执行程序的字节码的的行号的指示器。字节码解释器工作时通过改变程序计数器的值来改变将要选取的下一个指令。并且由于每个线程执行的运行的指令不同,每个线程有个字的程序计数器,并且互不干扰。所以程序计数器被称作是线程私有的。这种线程私有的区域有着很明显的特征就是生命周期和当前线程的周期保持一致。并且需要注意的是程序计数器是内存中唯一一个不会出现outOfMemory的区域。

程序计数器有两个作用:

A: 字节码解释器通过读取程序计数器的值来选取下一条将要执行的语句。

B:在多线程切换的情况下,记录当前线程执行的位置,方便于切换回时进程运行的位置。

虚拟机栈

Java内存可以粗糙的分为堆内存和栈内存,所谓的堆就是图中的java堆(Heap),而所谓的栈就是指的虚拟机栈(VM Stack)的局部变量表。虚拟机栈是由一个个栈针构成的,可以想为一个方法为一个栈针,随着方法的被调用完成(完成的条件有两个,一个是return,另外一个是发生异常),对应着方法的栈针出栈。栈针中都包含:局部变量表,操作数栈,动态链接和方法出口。局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)

虚拟机栈结构

本地方法栈

本地方法栈同样是线程私有的,区别是java虚拟机栈是用于java字节码的,而本地方法栈是用来执行Native方法的.结构都和虚拟机栈的结构相同.

Java堆

Java堆是整个虚拟机中最大的一块区域,是线程共享的区域,再虚拟机启动时创建,该块存在的唯一目的就是存放类的事例和数组.

方法区

和Java堆一样都是线程共享的区域,主要用于存放已经被虚拟机加载的类信息,常量,静态变量和已经被编译后的代码等数据.方法区和永久代的关系很像Java中接口和类的关系,类实现了接口,而永久代就是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。 也就是说,永久代是HotSpot的概念,方法区是Java虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久带这一说法。

运行时常量池

原来的运行时常量池是方法区的一部分,而在1.7之后,讲运行时常量池放到堆(Heap)中开辟了一个区域专门用来放运行时常量池.运行时常量池中遥遥包括两部分,一部分是实际存在的字面量,所谓字面量包括[1.文本字符串,2.被声明为final的常量,基本数据类型…..],另一部分是符号引用,包含[类和结构的名称,字段的名称,方法的名称]

运行时常量池

元数据区

替代了永久代,使用的是直接内存,什么是直接内存,它有能够干什么? 首先直接内存区域不是虚拟机运行时数据区的一部分,也不是虚拟机定义的一部分,但是经常会使用到.

JDK1.4 中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。

本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

对象的创建过程

对象的创建过程

对象的创建过程分为上图中的5步

  1. 类加载检查:当虚拟机遇到new的时候,会检查这个类是否存在,是否被加载,解释和初始化过,如果没有则进行类的初始化过程.
  2. 分配内存:再进行类加载检查后,会在堆内存中为新的对象分配内存,分配的规则有两种,一种是指针碰撞,另一种是空闲列表,针对不同的垃圾回收器会使用不同的方法.第一种方法指针碰撞的原理大致为它会将分配过的内存和未分配的内存间建立一个指针,当分配内存的时候,向为分配内存的地方移动新的对象的大小的位置,适用于比较规整的内存空间.第二种方法是维护一个分配内存的列表,每次分配内存都想列表中添加相关记录,表明这些对象在哪.
  3. 初始化零值:保证在分配完内存后,为分配的内存区域初始化为零值
  4. 设置对象头:虚拟机对对象的各种设置信息,保存在对象头中.
  5. 执行init方法:在执行init方法前,从虚拟机的角度来看其实对象已经生成了,但是在程序员的角度对象并没有生成,只有在init方法执行过后,对象才算生成.

对象的定位方式

Java虚拟机定位对象的方式主要有两种,一是使用句柄,二是直接使用指针.

悲观锁和乐观锁

Posted on 2019-04-23 | In Java , 多线程
Words count in article: 584 | Reading time ≈ 2

悲观锁和乐观锁

悲观锁

悲观锁是总会先去想最坏的情况,每次线程拿数据的时候都会认为他会进行修改,适用于的场景是多写少读,这时使用悲观锁效率会高。程序中典型的悲观锁时synchronized和ReentrantLock

乐观锁

乐观锁是总会先去想好的情况,每次线程拿数据只会看一眼,并不会做修改操作,这时没有必要加锁,程序中的atom原子类使用cas+volatile+native方法实现乐观锁。

两种锁的机制,有各自的好处,不能说谁好谁坏,各有优点:

悲观锁适用于多写少读的情况,而乐观锁适用于多读少写的情况,如果在多写的情况下使用乐观锁,会经常产生冲突,反而会降低性能。

乐观锁常用的两种方式

版本号的控制

每次读取数据的时候都会连同版本号一同读取,在当更新数据的时候,会比较读取到的版本号和数据库中的版本号是否一致,如果一致则更新,如果不一致则继续读取,更新数据。

CAS操作(compareAndSwap)

举个例子,在atomInteger类中涉及了CAS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x)
//这里是native方法 offset是偏移量先暂且不提,o是需要比较的值,expected是预期值,x是新值
// 如果使用程序写出来就是大概这样:
int value;
public boolean fakeCAS(int expect,int newValue){
if(value == expect){
value = newValue;
return true;
}else{
return false;
}
}

比较替换(compareAndSwap)是一个原子操作,也是一个自旋操作,等待一个周期再次尝试。

CAS操作的缺点

  1. ABA问题,何谓ABA:当两个线程读取到当前值为A,其中一个线程把值改为B后再把B改为A,这是另外一个线程使用cas操作读取到的值仍然是A,CAS会认为这个值从来没有变过,这就是ABA问题。也就是说遗失掉中间的过程,在链式的操作总更能体会到ABA问题的严重性。
  2. 自旋时间过长,消耗CPU资源。
  3. 只能对单个共享变量的原子性操作

线程池和AQS知识整理

Posted on 2019-04-23 | In 多线程
Words count in article: 3.1k | Reading time ≈ 13

线程池和AQS代码解析

使用线程池是为了将线程得以复用,线程创建后在线程池中被循环利用,防止了线程得多次创建和销毁.下面就通过代码来看线程池的工作原理.

线程池配置比较繁琐,所以java提供了一个线程池的工具类Executors 来使用静态的方法来创建拥有特定属性的线程池.下面先总体的看一下都有哪些创建线程池的方法,和各自的实现.

  1. newFixedThreadPool 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  2. newCachedThreadPool 创建一个可缓存的线程池,此时线程池对线程得多少没有限制,如果多了就定时回收,如果少了,就会只能添加
  3. newScheduledThreadPool 创建一个无限大小的线程池,此线程池可执行定时的任务
  4. newSingleThreadExecutor 线程池中有且只有一个线程执行任务,如果因异常结束,则会生成一个线程来替换原来的线程.此种连接池会按照任务的提交顺序依次执行.因为池中只有一个线程在工作.

在Excutors中有上面四种方式来创建线程池,那么这几种线程池具体是如何工作的呢?通过源码来一探究竟.

ThreadPoolExecutor

除了newScheduledThreadPool连接池没有使用ThreadPoolExcutor来实现,其他三种都使用到了ThreadPoolExcutor来进行连接池的实现和定制.下图是ThreadPoolExcutor的结构关系.它是ExecutorService的直接实现类.

5cc08171ca014

ThreadPoolExcutor提供了三种构造函数,不同的构造函数含有不同的参数.下面先了解一下各参数的含义

参数名称 参数类型 含义
corePoolSize Int 池中的线程数量,即使他们是空闲的,除非设置allowCoreThreadTimeOut参数
maximumPoolSize int 池中允许的最大的线程数量
keepAliveTime long 如果池中的线程超过核心数,如果超过这个时间线程仍是空闲的则结束这个线程
unit TimeUnit keepAilveTime的时间的单位
workQueue BlockingQueue 一个阻塞队列,用来存储未进行的任务.未执行的任务会一直在队列里,知道使用Excute方法执行.
threadFactory ThreadFactory 线程工厂,用于生产线程
handle RejectedExecutionHandler 执行阻塞的处理策略,造成阻塞的原因可能是达到了线程边界或者队列容量

其中线程池使用ctl变量控制状态信息,ctl是一个AtomInteger,其中高28位表示线程得数量,也就是workerCount,低位的4位表示线程池的状态.

名称 承担的任务
RUNNING 接受新的任务,并且处理队列中的任务
SHUTDOWN 不接受新的任务,但是会处理队列中的任务
STOP 不接受新任务,不处理排队的人物,并且终止正在进行的任务
TIDYING 所有任务已经终止,worker为空,通过terminate方法来转换为TIDYING
TERMINATED 当terminate方法执行过后的状态

Xnip2019-04-23_23-21-36

方法源码解析

提交的方法submit实在ExcutorService的实现类AbstarctExecutorService中所定义的,可以看出submit是excute方法的外壳,其实核心还是执行excute方法,但是会返回Future结果.

1
2
3
4
5
6
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;//返回Future结果
}

因为执行的核心方法是excute,所以进到ThreadPoolExecutor类中看excute方法是如何执行的.其实最通俗的解释excute的过程就是,将新的worker加入到worker的队列中,并执行当前的command线程.执行过程可以参考官方注释:

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
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/**
* 执行总共分为三步
* 1. 如果当前线程池中的工作线程数小于核心线程数,则添加一个worker来执行当前这个任务
* 2. 为了防止进入方法后线程池关闭或者线程挂掉需要重新获取ctl,并且进行判断,是否进行
* 回退或者是空池后添加一个空线程.
* 3.如果不能将任务放到队列中,则执行rollback,回退任务
*/
int c = ctl.get();//获得ctl变量,从而获得workercount和状态信息
if (workerCountOf(c) < corePoolSize) { //如果线程数小于核心线程数
if (addWorker(command, true)) //添加一个新的worker来执行任务
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
//如果当前池的状态为运行状态并且接受了这个任务,也就是说任务成功进入了执行队列
int recheck = ctl.get();
//重新获取ctl,因为有可能在进入这个if语句的时候一个线程挂了,或者整个池被关闭了
if (! isRunning(recheck) && remove(command)) //如果线程池的状态为非运行状态
// 从队列中移除任务,并且reject任务 相当于第一步操作的rollback
reject(command);
else if (workerCountOf(recheck) == 0) //如果池是空的
addWorker(null, false);//新建一个线程
}
else if (!addWorker(command, false))
//如果放到队列中失败,则将任务rollback
reject(command);
}

线程池中拥有的状态有以下几种:

1
2
3
4
5
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;

在excute方法中需要用到addWorker在当前状态和给定的边界条件下(核心线程数core或者最大线程数maximum)能否添加一个worker线程执行任务.如果当前线程池为空或者有资格关闭线程池.这是调用该方法会返回false.如果线程池工厂the thread factory不能再产出更多的线程也会返回false.

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
private boolean addWorker(Runnable firstTask, boolean core) {
/**
* firstTask 理解为初始化时给的第一个任务,可以绕过排队的过程,直接使用新的线程执行
* core 如果是true则使用corePoolSize作为边界,否则使用maximumPoolSize作为边界
*/
retry://flag标记
for (;;) {
int c = ctl.get(); //获取ctl变量
int rs = runStateOf(c);// 获取状态参数

// Check if queue empty only if necessary. 检查队列是否为空
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;

for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
//如果大于最大的CAPACITY或者wc大于核心线程数
return false;
if (compareAndIncrementWorkerCount(c))
//使用CAS添加worker后,操作成功后跳出循环
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);//以后单说这个worker
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
// 使用ReentrantLock上锁保持原子性
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());

if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)// 当前worker的数量大于largestPoolSize时
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();//线程开始执行
workerStarted = true;
}
}
} finally {
if (! workerStarted) //如果到这里线程还是没有开始,rollback添加worker的过程
addWorkerFailed(w);
}
return workerStarted;
}

其中涉及到了woker线程创建的rollback过程,调用了addWorkerFailed方法:

1
2
3
4
5
6
7
8
9
10
11
12
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);//如果存在在队列中移除w
decrementWorkerCount();//减少workerCount变量的值
tryTerminate();//重新检查终止过程,以防止因为这个worker的存在导致终止失败
} finally {
mainLock.unlock();
}
}

在当运行的线程数到达核心线程数后,就会将任务插入到workQueue里面,让池中的工作线程完成任务,上面的源码中使用addWoker方法来执行当前指定的任务,而未涉及到加入到workQueue中的任务是如何执行的,下面通过查看Woker中的run方法来查看池中的线程如何获取任务来执行的过程.

因为Worker实现AQS「AbstractQueuedSynchronizer」,并继承来Runnable,所以Worker其实本质上还是一个线程.在执行start后,同样会执行run方法.

Worker中的全局变量:

1
2
3
4
5
6
/** Thread this worker is running in.  Null if factory fails. 执行当前工作线程*/
final Thread thread;
/** 初始化的任务,可能是空*/
Runnable firstTask;
/** 每个线程的任务计数器(统计完成的任务数) */
volatile long completedTasks;

Worker的构造器,默认是需要传入Runnable的任务的.

1
2
3
4
5
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);//保存从ThreadFactory中获取到的Thread
}

worker中的方法

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
//查看是否是独占的
protected boolean isHeldExclusively() {
return getState() != 0; //AQS中的state保存这个状态
}
//尝试获得锁
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) { //通过改变state来实现 如果state>0说明获取到了锁
setExclusiveOwnerThread(Thread.currentThread()); //设置独占访问的线程
return true;
}
return false;
}
//尝试释放锁
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);//清空独占访问的线程
setState(0);//状态置为「释放锁」
return true;
}

public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }

void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}

在addWorke中调用run方法执行任务

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
/** Delegates main run loop to outer runWorker  */
public void run() {
runWorker(this); //委托runWorker执行
}

final void runWorker(Worker w) {
Thread wt = Thread.currentThread();//获得当前线程
Runnable task = w.firstTask;//获得任务
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) { //如果存在任务
w.lock(); //将当前工作线程上锁
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
//如果线程池正在被停止,需要确保线程已经被中断
//如果线程池没有被停止,需要确保线程不能被中断
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run(); //执行任务
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null; //清空任务
w.completedTasks++; //当前线程处理任务数加一
w.unlock();//解锁
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}

AQS「AbstractQueuedSynchronizer」 队列同步器

AQS的使用方式是继承,之类通过继承AQS,实现抽象方法来实现管理同步器. 其中上文中的ThreadPoolExcutor中的Worker就是继承AQS来实现的.

AQS使用一个全局变量state来表示当前的锁的状态,如果state>0,说明已经获取到了锁,如果state=0,说明释放了锁.

AQS依靠CLH队列完成对状态的管理.当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。

其中CLH的一个节点包括了:当前的状态,前驱,后继,当前节点保存的线程.

具体的AQS过程可以参考QAS原理

补充的countDownLatch和cyclicBarrier的区别

区别

Java中常用关键字总结

Posted on 2019-04-22 | In Java
Words count in article: 685 | Reading time ≈ 2

Java中关键字总结

final关键字

final主要用于三个地方:变量、方法、类

  • 变量: 如果final修饰变量,如果变量是基本数据类型,则表示如果该基本数据类型一旦初始化则不能再改变,如果修饰引用变量,则表示初始化之后不能执行另外一个变量。
  • 方法: 如果final作用域方法上,说明该方法是不能改变的,也就是说继承累无法改变方法。所有private方法默认是final的
  • 类:如果final作用与类上,说明该类是无法被继承的。并且他的类中的方法默认都是private的。

static关键字

static关键字在以下四种情况下用到。

  • 用来修饰成员变量或方法,通过static修饰的变量或者方法被称作为静态变量和静态方法。可以通过类名.变量或方法()调用,静态变量和方法属于整个类对象,而不属于实例变量。静态变量存放在java内存中的方法区。
  • 用来修饰静态代码块。使用static修饰的静态代码块,会先于代码块和构造方法。并且只会执行一次。
  • 用来修饰静态内部类。使用static修饰静态内部类,有两个特性,其一是他的创建不依赖于外部类,其二是他只能调用外部类的静态变量或方法。
  • 用来静态导入包,这个不太常用。使用 import static导入静态资源。

this 关键字

this用于执行当前对象,也就是说不能在static方法或者代码块中使用this来指向对象。

super 关键字

this关键字指向本身对象,而super关键字则指向父类对象。

java中二进制运算操作

与运算 &

转换为二进制,都为一时则为一

或运算 |

转换为二进制后,只要有一个为一,则结果为一

异或操作 ^

转换为二进制后,不同则为一

取反操作 ~

按位取反的操作(由正数变为负数)

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
@Test
public void testBitOperating(){
System.out.println(5&6);
// 0101
// 0110
// -----
// 0100 结果是4
System.out.println(5 | 6);
// 0101
// 0110
// -----
// 0111 结果是7
System.out.println(5 ^ 6);
// 0101
// 0110
// -----
// 0011 结果是 3
System.out.println(~5);
// 0.... 0101
//-----------
// 0.... 0101 补码 = 正数的补码和源码相同 负数的补码等于源码取反后+1
// 1.... 0110 源码 = 取反 后加一 -6
System.out.println(~-6);
// 1.....0110
// 1.....1010 补码:负数的补码为反码+1
// 0.....0101 源

如何理解和使用synchronized关键字

Posted on 2019-04-22 | In 多线程
Words count in article: 1.6k | Reading time ≈ 6

synchronized

为什么要用synchronized

多线程有三大特性:原子性,有序性(重排序问题?)和可见性;其中原子性在程序上的体现就是使用synchronized,为了保证可见性是使用需要使用volatile.使用synchronized来保证程序的原子性

首先在讨论三大特性前需要知道java的内存模型和happen-before原则。内存模型与happen-before原则

牵扯出的问题:

  1. 什么是线程安全:我的理解就是如果保证多线程执行的结果和预期结果相同,就是线程安全的,否则就是线程不安全的

  2. 什么是JAVA内存模型?

    在java内容中程序计数器,虚拟机栈,本地方法栈都是线程私有的区域,而方法区和java堆是线程共享的区域

    因为cpu的处理速度与内存的读写速度之间有着着巨大的差距,所以在cpu和内存间添加一层缓存,每次现在cpu的缓存中,这个缓存区域的读写速度往往比内存的读写速度快很多,这样就能够缓解cpu和主存之间的速度差距。

    所以每次cpu读写数据前,会先读取共享变量到本地内容,cpu再从本地内存中读取数据。这就是java的内存模型

    这样也会产生数据不一致的情况,这是就要需要保证共享变量的数据可见性,即线程1修改了变量,则线程2再修改变量前已经得知变量已经被修改了。

    应用synchronized

为了保证程序的原子性:我们在程序中使用synchronized关键字对程序进行锁定,表示在锁住的区域内只能有一个线程访问。

如何使用synchronized

  • 使用synchronized修饰静态方法,针对类变量进行加锁。进入方法的前提是获得类的锁。
  • 修饰实例方法,针对实例对象,在进入方法前要获得实例变量的锁。
  • 修饰代码块。指定加锁的对象,在进入代码块钱要先获得指定对象的锁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SynchronizedTest {

//synchronized的三种用法
//1.修饰实例方法 需要拥有实例对象的锁
public synchronized void hello(){
System.out.println("hello");
}
//2.修饰静态方法 需要拥有类对象的锁
public synchronized static void staticHello(){
System.out.println("staticHello");
}
public void blockHello(){
//3.修饰代码块 需要拥有【this】的锁
synchronized(this){
System.out.println("code block hello");
}
}
}

典型的使用synchronized的场景–单例模式的双重锁结构

在讨论双重锁的前,需要聊聊单例模式,单例模式通俗的来说一个类只能构造一个实例。针对的实现又两种:1.饿汉模式,2.懒汉模式

饿汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private Singleton(){};
//1. 使用静态变量
// private static Singleton singleton = new Singleton();
//2.使用静态代码块 未使用时造成内存空间浪费
private static Singleton singleton;
static {
singleton = new Singleton();
}
public Singleton newInstance(){
return singleton;
}
}

这种方式不会影响到多线程的线程安全问题,因为类的装载机制在初始化对象的时候是保证不会有第二个线程进入的。但是有个很大的弊端是他不是lazy-loading的,这回产生资源的浪费,比如创建完对象后,自始至终没用过。所以不推荐使用

懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton {
private void Singleton(){}
private static Singleton singleton;

// 普通的线程不安全的
public Singleton newInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}

//2.静态内部类 一方面能够达到lazy-loading的效果,另一方面能够保证线程安全,因为jvm保证初始化的时候别的线程是不能进入的
private static class newSingleton{
private static final Singleton INSTANCE = new Singleton();
}

public Singleton newInstanceInnerClass(){
return newSingleton.INSTANCE;
}
}

上面代码的第一种是不能保证线程安全的,多线程下会导致失效。而第二种使用静态内部类的方式实现能够实现线程安全得宜于类的加载机制,类似饿汉模式。但同时具有lazy-Loading的特性。是常用的单例模式用法。

double-check 双重锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DoubleCheck {
private void DoubleCheck(){}
private volatile DoubleCheck doubleCheck; //使用volatile保证原子性,防止重排序
public DoubleCheck newInstance(){
if(doubleCheck == null){
// 当两个线程同时到这步 a先进同步块,在a进入后获得instance后,b获得锁,进入同步块,
// 这时候下一个判断就起到作用了,这时候的doubleCheck不为空,
// 直接return,否则又️新建了一个对象
synchronized (DoubleCheck.class){
if(doubleCheck == null){
doubleCheck = new DoubleCheck();
}
}
}
return doubleCheck;
}
}

实际上double-check也是懒汉模式的一种,能够保证线程安全。很完美。。

synchronzied 底层实现

synchronized 同步语句块的实现使用的是monitorenter 和monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

volatile

volatile是java最轻量级的同步机制。

volatile 特性

  1. volatile作用与变量,用来保证变量的在各线程之间的可见性,使用volatile修饰变量,表示变量不稳定,每次都需要写入,并重新获取.所以每次都是获取的都是主存内最新的变量的值.
  2. 禁止指令重排列优化
  3. volatile 只能保证可见性,不能保证原子性.

volatile和synachronized的区别

  1. volatile是线程同步的轻量级实现,所以说使用volatile的性能肯定要强于synchronized。
  2. volatile作用于变量,而synachronzied作用于方法和代码块。
  3. 多线程间使用volatile不会发生阻塞,而使用synachronized可能发生阻塞
  4. volatile保证变量在多线程间的可见性,而synchronized既能够保证可见性,又能保证原子性。
<i class="fa fa-angle-left"></i>1…678…12<i class="fa fa-angle-right"></i>
NanYin

NanYin

Was mich nicht umbringt, macht mich starker.

111 posts
16 categories
21 tags
RSS
GitHub E-Mail
近期文章
  • ThreadLocal
  • Java的四种引用类型
  • Markdown语法入门
  • IDEA设置代码注释模板和JavaDoc文档生成
  • 基于Java的WebService实践
0%
© 2023 NanYin
|
本站访客数:
|
博客全站共140.1k字
|