晚来天欲雪,能饮一杯无

个人记录


  • 首页

  • 分类

  • 归档

  • 标签

  • 搜索

Spring原理(二)--IOC 原理

发表于 2020-04-26

概念

  Spring通过一个配置文件描述Bean及Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。Spring的IoC容器在完成这些底层工作的基础上,还提供了Bean实例缓存、生命周期管理、Bean实例代理、事件发布、资源装载等高级服务。

Spring容器高层视图

  Spring启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。其中Bean缓存池为HashMap实现。
avatar

IOC容器实现

BeanFactory-框架基础设施

  BeanFactory是Spring框架的基础设施,面向Spring本身;ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合我们都直接使用ApplicationContext而非底层的BeanFactory。
avatar

BeanDefinitionRegistry注册表

  Spring配置文件中每一个节点元素在Spring容器里都通过一个BeanDefinition对象表示,它描述了Bean的配置信息。而BeanDefinitionRegistry接口提供了向容器手工注册BeanDefinition对象的方法。

BeanFactory顶层接口

  位于类结构树的顶端,它最主要的方法就是getBean(String beanName),该方法从容器中返回特定名称的Bean,BeanFactory的功能通过其他的接口得到不断扩展。

ListableBeanFactory

  该接口定义了访问容器中Bean基本信息的若干方法,如查看Bean的个数、获取某一类型Bean的配置名、查看容器中是否包括某一Bean等方法。

HierarchicalBeanFactory父子级联

  父子级联IoC容器的接口,子容器可以通过接口方法访问父容器;通过HierarchicalBeanFactory接口,Spring的IoC容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的Bean,但父容器不能访问子容器的Bean。Spring使用父子容器实现了很多功能,比如在SpringMVC中,展现层Bean位于一个子容器中,而业务层和持久层的Bean位于父容器中。这样,展现层Bean就可以引用业务层和持久层的Bean,而业务层和持久层的Bean则看不到展现层的Bean。

ConfigurableBeanFactory

  ConfigurableBeanFactory是一个重要的接口,增强了IoC容器的可定制性,它定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法;

AutowireCapableBeanFactory自动装配

  定义了将容器中的Bean按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法。

SingletonBeanRegistry运行期间注册单例Bean

  定义了允许在运行期间向容器注册单实例Bean的方法;对于单实例(singleton)的Bean来说,BeanFactory会缓存Bean实例,所以第二次使用getBean()获取Bean时将直接从IoC容器的缓存中获取Bean实例。Spring在DefaultSingletonBeanRegistry类中提供了一个用于缓存单实例Bean的缓存器,它是一个用HashMap实现的缓存器,单实例的Bean以beanName为键保存在这个HashMap中。

依赖日志框框

  在初始化BeanFactory时,必须为其提供一种日志框架,比如使用Log4J,即在类路径下提供Log4J配置文件,这样启动Spring容器才不会报错。

ApplicationContext面向开发应用

  ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。ApplicationContext继承了HierarchicalBeanFactory和ListableBeanFactory接口,在此基础上,还通过多个其他的接口扩展了BeanFactory的功能。

  1. ClassPathXmlApplicationContext:默认从类路径加载配置文件
  2. FileSystemXmlApplicationContext:默认从文件系统中装载配置文件
  3. ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。
  4. MessageSource:为应用提供i18n国际化消息访问的功能;
  5. ResourcePatternResolver:所有ApplicationContext实现类都实现了类似于PathMatchingResourcePatternResolver的功能,可以通过带前缀的Ant风格的资源文件路径装载Spring的配置文件。
  6. LifeCycle:该接口是Spring2.0加入的,该接口提供了start()和stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext实现及具体Bean实现,ApplicationContext会将start/stop的信息传递给容器中所有实现了该接口的Bean,以达到管理和控制JMX、任务调度等目的。
  7. ConfigurableApplicationContext扩展于ApplicationContext,它新增加了两个主要的方法:refresh()和close(),让ApplicationContext具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用refresh()即可启动应用上下文,在已经启动的状态下,调用refresh()则清除缓存并重新装载配置信息,而调用close()则可关闭应用上下文。

avatar

WebApplication体系架构

  WebApplicationContext是专门为Web应用准备的,它允许从相对于Web根目录的路径中装载配置文件完成初始化工作。从WebApplicationContext中可以获得ServletContext的引用,整个Web应用上下文对象将作为属性放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。
avatar

SpringBean作用域

  Spring中为Bean定义了5种作用域,分别为singleton(单例)、prototype(原型)、request、session和global session。

singleton:单例模式(多线程下不安全)

  singleton:单例模式,Spring IoC容器中只会存在一个共享的Bean实例,无论有多少个Bean引用它,始终指向同一对象。该模式在多线程下是不安全的。Singleton作用域是Spring中的缺省作用域,也可以显示的将Bean定义为singleton模式,配置为:

1
<bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>

prototype:原型模式每次使用时创建

  prototype:原型模式,每次通过Spring容器获取prototype 定义的bean时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态,而singleton全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。

Request:一次request一个实例

  request:在一次 Http 请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效,当前Http 请求结束,该bean实例也将会被销毁。

1
<bean id="loginAction" class="com.cnblogs.Login" scope="request"/>

session

  session:在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。同Http请求相同,每一次session请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的session请求内有效,请求结束,则实例将被销毁。

global Session

  global Session:在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。

Spring Bean生命周期

  1. 实例化一个Bean,也就是我们常说的new。
  2. 按照Spring上下文对实例化的Bean进行配置,也就是IOC注入。
  3. 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring 配置文件中Bean的id值。
  4. 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory,setBeanFactory(BeanFactory)传递的是 Spring 工厂自身(可以用这个方式来获取其它 Bean,只需在Spring配置文件中配置一个普通的Bean就可以)。
  5. 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法)
  6. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术。
  7. 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。
  8. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法。
    注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Sprin 配置文件中也可以配置非Singleton。
  9. 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法;
  10. 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
  11. bean标签有两个重要的属性(init-method 和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。
    1
    <bean id="" class="" init-method="初始化方法" destroy-method="销毁方法">

avatar

Spring依赖注入四种方式

  1. 构造器注入

    1
    2
    3
    4
    5
    6
    7
    /*带参数,方便利用构造器进行注入*/
    public CatDaoImpl(String message){
    this. message = message;
    }
    <bean id="CatDaoImpl" class="com.CatDaoImpl">
    <constructor-arg value=" message "></constructor-arg>
    </bean>
  2. setter方法注入

    1
    2
    3
    4
    5
    6
    public class Id {
    private int id;
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    }
    <bean id="id" class="com.id "> <property name="id" value="123"></property> </bean>
  3. 静态工厂注入
      静态工厂顾名思义,就是通过调用静态工厂的方法来获取自己需要的对象,为了让spring管理所有对象,我们不能直接通过”工程类.静态方法()”来获取对象,而是依然通过spring注入的形式获取。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class DaoFactory { //静态工厂
    public static final FactoryDao getStaticFactoryDaoImpl(){
    return new StaticFacotryDaoImpl();
    }
    }
    public class SpringAction {
    private FactoryDao staticFactoryDao; //注入对象
    //注入对象的 set 方法
    public void setStaticFactoryDao(FactoryDao staticFactoryDao) {
    this.staticFactoryDao = staticFactoryDao;
    }
    }
    //factory-method="getStaticFactoryDaoImpl"指定调用哪个工厂方法
    <bean name="springAction" class=" SpringAction" >
    <!--使用静态工厂的方法注入对象,对应下面的配置文件-->
    <property name="staticFactoryDao" ref="staticFactoryDao"></property>
    </bean>
    <!--此处获取对象的方式是从工厂类中获取静态方法-->
    <bean name="staticFactoryDao" class="DaoFactory" factory-method="getStaticFactoryDaoImpl"></bean>
  4. 实例工厂
      实例工厂的意思是获取对象实例的方法不是静态的,所以你需要首先new工厂类,再调用普通的实例方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class DaoFactory { //实例工厂
    public FactoryDao getFactoryDaoImpl(){
    return new FactoryDaoImpl();
    }
    }
    public class SpringAction {
    private FactoryDao factoryDao; //注入对象
    public void setFactoryDao(FactoryDao factoryDao) {
    this.factoryDao = factoryDao;
    }
    }
    <bean name="springAction" class="SpringAction">
    <!--使用实例工厂的方法注入对象,对应下面的配置文件-->
    <property name="factoryDao" ref="factoryDao"></property>
    </bean>
    <!--此处获取对象的方式是从工厂类中获取实例方法-->
    <bean name="daoFactory" class="com.DaoFactory"></bean>
    <bean name="factoryDao" factory-bean="daoFactory" factory-method="getFactoryDaoImpl"></bean>

5种不同方式的自动装配

  Spring装配包括手动装配和自动装配,手动装配是有基于xml装配、构造方法、setter方法等自动装配有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入。

  1. no:默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配。
  2. byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
  3. byType:通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
  4. constructor:这个方式类似于byType,但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
  5. autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。

spring原理(一)

发表于 2020-04-21

spring是一个全面的、企业应用开发一站式的解决方案,贯穿表现层、业务层、持久层。而且Spring仍然可以和其他的框架无缝整合。

Spring特点

  • 轻量级
    从大小和开销两个方面而言spring都是轻量级的,完整的spring框架可以在一个大小只有1M多的jar文件中发布,并且spring所需的处理开销也是微不足道的。
  • 控制反转
    spring通过一种乘坐控制反转IOC的技术促进了低耦合。当应用了IOC,一个对象依赖的其他对象会通过被动的方式传递进来,而不是这对象资金创建或者查找一栏对象。
  • 面向切面
    spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
  • 容器
    spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建—基于一个可配置的原型,你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例–以及它们是如何互相关联的。
  • 框架集合
    spring可以将简单的组件配置,组合成复杂的应用。在spring中,应用对象被声明式的组合,典型的是在一个xml文件中。spring也提供了很多基础功能(事务管理,持久化框架集成等),将应用逻辑的开发留给开发者。
  • 方便解耦,简化开发
  • 声明式事务的支持
  • 降低Java EE API的使用难度

avatar

Spring核心组件

avatar

Spring常用模块

  • 核心容器
    核心容器提供spring框架的基本功能,核心容器的主要组件是BeanFactory,他是工厂模式的实现,BeanFactory使用控制反转模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring上下文
    Spring上下文是一个配置文件,向Spring框架提供上下文信息,Spring上下文包括企业服务,例如:JNDI,EJB,电子邮件,国际化,校验和调度功能。
  • Spring AOP
    通过配置管理特性,SpringAOP模块直接向面向切面的编程功能集成到了Spring框架中,可以将一些通用任务,如安全,事物,日志,等集中进行管理,提高了复用性和管理的便捷性。
  • Spring DAO
    为JDBC DAO抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息,异常层次结构简化了错误处理,并且极大的降低了需要便携的异常代码数量,SpringDAO的面向JDBC的异常存从通用的DAO异常层次结构。
  • Spring ORM
    spring框架插入了若干和ORM框架,从而提供了ORM的对象关系工具,其中包括JDO,Hibernate,IBatista SQL Map。所有这些都遵从了Spring的通用实物和DAO的异常曾子结构。
  • Sping Web 模块
    Web上下文模块创建在应用程序上下文模块之上,为基于Web的应用程序创建了提供上下文,所以Srping框架支持Jakarta Struts的集成,Web模块还减缓了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC框架
    MVC框架是衣蛾全功能的构建Web应用程序的MVC实现,通过策略接口,MVC框架编程了高度可配置的,MVC容纳了大量的视图技术,其中包括JSP,Velocity,Ties,iTest和POI。

avatar

Spring主要包

core—-Spring核心工具包,其他包依赖此包
beans—-所有应用都用到,包含访问配置文件,创建和管理bean等。
aop—-Spring的面向切面编程,提供AOP(面向切面编程)的实现
context—-提供在基础IOC功能上的扩展服务,此外还提供许多企业级服务的支持,有邮件服务、任务调度。JNDI定位,EJB集成,远程访问,缓存以及多种视图层框架的支持。
web.mvc—-包含SpringMVC应用开发时所需的核心类。
transaction—-为JDBC,Hibernate,JDO,JPA提供一致的声明式和编程式事务管理
web—–包含Web应用开发时,用到Spring关键时所需的核心类。
aspects—-Spring提供的对AspectJ框架的整合
test—-对JUNIT等测试框架的简单封装
asm—-spring3.0开始提供自己独立的asm jar包
context.support—-spring context的扩展支持,用于MVC方面
expression—-Spring表达式语言
instrument.tomcat—-Spring对tomcat连接池的集成
instrument—-Spring对服务器的代理接口
jdbc—-为JDBC的简单封装
jms—-为简化jms api的使用而做的简单封装
orm—-整合第三方的orm实现,如hibernate,ibatis,jdo,jpa等
oxm—-Spring对于object/xml映射的支持,可以让java与xml来回切换
web.portlet—-Spring MVC的增强
web.servlet—-对J2EE6.0 servlet3.0的支持
web.struts—-整合对struts框架的支持,更方便更容易的集成Struts框架。
avatar

Spring 常用注解

bean注入与装配的的方式有很多种,可以通过xml,get set方式,构造函数或者注解等。简单易用的方式就是使用Spring的注解了,Spring提供了大量的注解方式。
【1】
@Controller
用于标注控制层组件,用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller对象,分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping注解,可以把Request请求header部分的值绑定到方法的参数上
@RestController
相当于@Controller和ResponseBody的组合效果
@Component
泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
@Respository
用于注解dao,在daoimpl类上注解。
@Service
用于标注业务层组件
【2】
@ResponseBody
异步请求,该注解用于将Controller的方法返回的对象。通过适当的HttpMessageConverter转换为指定格式后,写入Response对象的body数据区。返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml)使用
@RequestMapping
一个用来处理请求地址映射的注解,可用于类或方法上,用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径
@Aurowired
可以对类成员变量、方法及构造函数进行标注、完成自动装配的工作,通过@Autowired的使用来消除setget方法
@PathValiable
用于将请求URL中的模板变量映射到功能处理方法的参数上,即去除url模板中的变量作为参数
@RequestParam
主要用于在SpringMVC后台控制层获取参数,类似一种是request.getParameter(“name”)
@RequestHeader
而言吧Request请求header部分的值绑定到方法的参数上
【3】
@ModelAttribute
该Controller的所有方法在调用前,先执行此@ModelAttribute方法,可用于注解和方法参数中,可以把这个@ModelAttribute特征,应用在BaseController当中,所有Controller继承BaseController,即可实现在调用Controller时,先执行@ModelAttibute方法。
@SessionAttributes
即将值放在session作用域中,卸载class上面。
@Valid
实体数据校验,可以结合hibernate validator以前使用
@CookieValue
用来获取Cookie中的值
avatar

装配注解比较

【包区别】
@Resource javax.annotation java
@Inject javax.inject java
@Qualifier javax.inject java
@Autowired org.springframework.bean.factory java
【@Autowored和@Inject】
二者基本相同,但@Autowird不能离开spring环境
1.按照类型匹配 2.按照限定符进行类型限定 3.按照名称匹配
【@Resource】
1.按照名称匹配,2.按照类型匹配 3.按照限定符进行类型限定(但如果名称匹配成功的话这条会被忽略)
avatar

Spring 第三方结合

【权限】
shiro,java的一个安全框架,认证,授权,加密,会话管理,与Web集成,缓存以及多种视图层框架的支持。
【缓存】
redis:一个开源的使用ANSI C语言填写,支持网络,可基于内存亦可持久化的日志型,Key-value数据库
【持久化框架】
Mybatis:是支持普通Sql查询,存储过程和高级映射的优秀持久层框架
【定时任务】
quartz:一个开源的作业调度框架,由java编写,在.NET平台为Quartz.net通过Quart可以快速的完成任务调度的工作。
Spring-Task:轻量型的Quartz,而且使用起来简单,除spring相关的包外不需要额外的包,而且支持注解和配置文件两种形式。
【校验框架】
Oval:oval是一个可扩展的的java对象数据验证框架,验证的规则可以通过配置文件,Annotation,POJOs进行设定,可以使用纯java语言,javaScript,Groovy,BeanShell等进行规则的编写。
avatar

java基础(五)

发表于 2020-04-20

java序列化

保存(持久化)对象及其状态到内存或者磁盘

  Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。

序列化对象以字节数组保持-静态成员不保存

  使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。

序列化用户远程对象传输

  除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。

Serializable 实现序列化

  在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。
  通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化。
  在类中增加writeObject和readObject方法可以实现自定义序列化策略。
  虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID是否一致(就是private static final long serialVersionUID)

序列化并不保存静态变量

  要想将父类对象也序列化,就需要让父类也实现Serializable接口

Transient关键字

  Transient关键字阻止该变量被序列化到文件中

  1. 在变量声明前加上Transient关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如int型的是0,对象型的是null。
  2. 服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

JAVA复制

  将一个对象的引用复制给另外一个对象,一共有三种方式。第一种方式是直接赋值,第二种方式是浅拷贝,第三种是深拷贝。这三种概念实际上都是为了拷贝对象。

直接赋值复制

  在Java 中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是说a1和a2指向的是同一个对象。因此,当a1变化的时候,a2里面的成员变量也会跟着变化。

浅复制(复制引用但不复制引用的对象)

  创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

深复制(复制对象和其应用对象)

  深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。

序列化(深clone一中实现)

  在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。

java基础(四)

发表于 2020-04-20

JAVA内部类

  Java类中不仅可以定义变量和方法,还可以定义类,这样定义在类内部的类就被称为内部类。根据定义的方式不同,内部类分为静态内部类,成员内部类,局部内部类,匿名内部类四种。

静态内部类

  定义在类内部的静态类,就是静态内部类。

  1. 静态内部类可以访问外部类所有的静态变量和方法,即使是private的也一样。
  2. 静态内部类和一般类一致,可以定义静态变量、方法,构造方法等。
  3. 其它类使用静态内部类需要使用”外部类.静态内部类”方式,如下所示:Out.Inner inner =new Out.Inner();inner.print();
  4. Java集合类HashMap内部就有一个静态内部类Entry。Entry是HashMap存放元素的抽象,HashMap内部维护Entry数组用了存放元素,但是Entry对使用者是透明的。像这种和外部类关系密切的,且不依赖外部类实例的,都可以使用静态内部类。

成员内部类

  定义在类内部的非静态类,就是成员内部类。成员内部类不能定义静态方法和变量(final修饰的除外)。这是因为成员内部类是非静态的,类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的。

局部内部类(定义在方法中的类)

  定义在方法中的类,就是局部类。如果一个类只在某个方法中使用,则可以考虑使用局部类。

匿名内部类(要继承一个父类或者实现一个接口,直接使用new来生成一个对象的引用)

  匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。


JAVA泛型

  泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
  泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
  比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用Java泛型。

泛型方法()

  可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

1
2
1. <? extends T>表示该通配符所代表的类型是T类型的子类。
2. <? super T>表示该通配符所代表的类型是T类型的父类。

泛型类

  泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
  出于规范的目的,Java还是建议用单个大写字母来代表类型参数。常见的如:
T 代表一般的任何类。
E 代表Element的意思,或者Exception异常的意思。
K 代表Key的意思。
V 代表Value的意思,通常与K一起配合使用。
S 代表Subtype的意思。

类型通配符?

  通配符的出现是为了指定泛型中的类型范围。
  通配符有3种形式:
<?>被称作无限定的通配符。
<? extends T>被称作有上限的通配符。
<? super T>被称作有下限的通配符。

无限定的通配符

  无限定通配符经常与容器类配合使用,它其中的 ? 其实代表的是未知类型,所以涉及到 ? 时的操作,一定与具体类型无关。
  例如 List<?> 在逻辑上是List,List 等所有 List<具体类型实参>的父类。

类型擦除

  Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List和 List等类型,在编译之后都会变成 List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。

类型擦除带来的局限性

  类型擦除,是泛型能够与之前的java版本代码兼容共存的原因。但也因为类型擦除,它会抹掉很多继承相关的特性,这是它带来的局限性。
  泛型类或者泛型方法中,不接受8种基本数据类型。
  Java不能创建具体类型的泛型数组。
  但是利用类型擦除的原理,用反射的手段就绕过了正常开发中编译器不允许的操作限制。

泛型好处

  泛型抽离了数据类型与代码逻辑,本意是提高程序代码的简洁性和可读性,并提供可能的编译时类型转换安全检测功能。
  而类型擦除,使泛型能够与之前的java版本代码兼容共存。

java基础(三)

发表于 2020-04-19

JAVA注解

  Annotation(注解)是Java提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation对象,然后通过该Annotation对象来获取注解中的元数据信息。

4种标准元注解

  元注解的作用是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型作说明。
@Target 修饰的对象范围
  @Target说明了Annotation所修饰的对象范围:Annotation可被用于packages、types(类、接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch 参数)。在Annotation类型的声明中使用了target 可更加明晰其修饰的目标。
@Retention 定义 被保留的时间长短
  Retention定义了该Annotation被保留的时间长短:表示需要在什么级别保存注解信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效),取值(RetentionPoicy)由:

  • SOURCE:在源文件中有效(即源文件保留)
  • CLASS:在 class 文件中有效(即 class 保留)
  • RUNTIME:在运行时有效(即运行时保留)

@Documented描述-javadoc
  @Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
@Inherited 阐述了某个被标注的类型是被继承的
  @Inherited元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
avatar

注解处理器

  如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。下面实现一个注解处理器。

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
 /*1:*** 定义注解*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/** 供应商编号 */
public int id() default -1;
/*** 供应商名称 */
public String name() default "";
/** * 供应商地址 */
public String address() default "";
}
/*2:注解使用*/
public class Apple {
@FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路")
private String appleProvider;
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
}
/*3:*********** 注解处理器 ***************/
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz) {
String strFruitProvicer = "供应商信息:";
Field[] fields = clazz.getDeclaredFields();// 通过反射获取处理注解
for (Field field : fields) {
if (field.isAnnotationPresent(FruitProvider.class)) {
FruitProvider fruitProvider = (FruitProvider)field.getAnnotation(FruitProvider.class);
// 注解信息的处理地方
strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:" + fruitProvider.name() + " 供应商地址:"
+ fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
/*4:*********** 应用 ***************/
public class FruitRun {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
/*********** 输出结果 ***************/
// 供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延
}
}

Java 8中注解新特性

  • @Repeatable 元注解,表示被修饰的注解可以用在同一个声明式或者类型加上多个相同的注解(包含不同的属性值)
  • @Native 元注解,本地方法
  • java8中Annotation可以被用在任何使用Type的地方

进阶

https://www.jianshu.com/p/89c07ce0c99c

加密算法--AES

发表于 2020-04-17

java实现AES加密算法,随机iv值合入到加密文件中,使得每次加密的内容不一样。
核心代码如下。

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
public static final int AES_KEY_SIZE = 256; // in bits
public static final int GCM_NONCE_LENGTH = 12; // in bytes
public static final String CIPHER = "AES";
public static final String AES_GCM_NOPADDING = "AES/GCM/NoPadding";// java 8-u162 以后版本才支持
public static final String AES_GCM_PKCS5PADDING = "AES/GCM/PKCS5Padding";// java 8-u162 以后版本才支持
public static final String AES_CBC_NOPADDING = "AES/CBC/NoPadding";
public static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5Padding";
/**
* 生成秘钥的方法
*
* @param encryptPass
* @return
*/
public static SecretKey getSecreKey(String encryptPass) {
SecretKey key = null;
try {
KeyGenerator keyGen = KeyGenerator.getInstance(CIPHER);
keyGen.init(AES_KEY_SIZE, new SecureRandom(encryptPass.getBytes(StandardCharsets.UTF_8)));
key = keyGen.generateKey();
System.out.println(Base64.getEncoder().encodeToString(key.getEncoded()));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return key;
}
/**
* AES加密
*
* @param content
* @param encryptPass
* @return
*/
public static String encrypt(String content, String encryptPass) {
try {
Cipher cipher = Cipher.getInstance(AES_GCM_PKCS5PADDING);
cipher.init(Cipher.ENCRYPT_MODE, getSecreKey(encryptPass));
byte[] iv = cipher.getIV();
byte[] encryptData = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
byte[] message = new byte[12 + content.getBytes(StandardCharsets.UTF_8).length + 16];
System.arraycopy(iv, 0, message, 0, 12);
System.arraycopy(encryptData, 0, message, 12, encryptData.length);
return Base64.getEncoder().encodeToString(message);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException
| InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
/**
* AES解密
*
* @param base64Content
* @param encryptPass
* @return
* @throws IllegalBlockSizeException
*/
public static String decrypt(String base64Content, String encryptPass) throws IllegalBlockSizeException {
byte[] content = Base64.getDecoder().decode(base64Content);
if (content.length < 12 + 16) {
throw new IllegalBlockSizeException();
}
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, content, 0, 12);
try {
Cipher cipher = Cipher.getInstance(AES_GCM_PKCS5PADDING);
cipher.init(Cipher.DECRYPT_MODE, getSecreKey(encryptPass), spec);
byte[] decryptData = cipher.doFinal(content, 12, content.length - 12);
return new String(decryptData);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException
| InvalidKeyException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return null;
}

注意:GCM模式java 8-u162 版本以后才支持,以前的版本不能使用该加密模式。
不建议使用ECB模式,ECB模式现阶段存在破解的可能。

java基础(二)

发表于 2020-04-15

JAVA反射

先了解一下动态语言,动态语言是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的JavaScript就是动态语言,除此之外 Ruby,Python 等也属于动态语言,而C、C++则不属于动态语言。从反射角度说JAVA属于半动态语言。

反射机制概念(运行状态中知道类所有的属性和方法)

avatar
在Java中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。

反射的应用场合

编译时类型和运行时类型

  在Java程序中许多对象在运行是都会出现两种类型:编译时类型和运行时类型。编译时的类型由声明对象时实用的类型来决定,运行时的类型由实际赋值给对象的类型决定。如:Person p=new Student();其中编译时类型为Person,运行时类型为Student。

编译时类型无法获取具体方法

  程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为Object,但是程序有需要调用该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了。

Java反射API

  反射API用来生成JVM中的类、接口或则对象的信息

  1. Class类:反射的核心类,可以获取类的属性,方法等信息。
  2. Field类:Java.lang.reflec包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
  3. Method类:Java.lang.reflec包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
  4. Constructor类:Java.lang.reflec包中的类,表示类的构造方法。

反射使用步骤(获取 Class 对象、调用对象方法)

  1. 获取想要操作的类的Class对象,他是反射的核心,通过Class对象我们可以任意调用类的方法。
  2. 调用Class类中的方法,既就是反射的使用阶段。
  3. 使用反射API来操作这些信息。

获取Class对象的3种方法

  1. 调用某个对象的 getClass()方法 Person p=new Person();Class clazz=p.getClass();
  2. 调用某个类的class属性来获取该类对应的Class对象。Class clazz=Person.class;
  3. 使用Class类中的forName()静态方法(最安全/性能最好)Class clazz=Class.forName(“类的全路径”); (最常用)

创建对象的两种方法

  1. Class对象的newInstance()。
    使用Class对象的newInstance()方法来创建该Class对象对应类的实例,但是这种方法要求该Class对象对应的类有默认的空构造器。
  2. 调用Constructor(构造器)对象的newInstance()
    先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建Class对象对应类的实例,通过这种方法可以选定构造方法创建实例。
    1
    2
    3
    4
    5
    6
    7
    8
    //获取 Person 类的 Class 对象
    Class clazz=Class.forName("reflection.Person");
    //使用newInstane() 方法创建对象
    Person p=(Person) clazz.newInstance();
    //获取构造方法(Constructor)并创建对象
    Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
    //创建对象并设置属性
    Person p1=(Person) c.newInstance("李四","男",20);

java基础(一)

发表于 2020-04-15

JAVA异常分类及处理

  如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。
avatar

异常分类

  Throwable是Java语言中所有错误或异常的超类。下一层分为Error和Exception。

Error

  Error类是指java运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。

Exception(RuntimeException、CheckedException)

  Exception又有两个分支,一个是运行时异常RuntimeException,一个是CheckedException。

RuntimeException

  比如NullPointerException、ClassCastException。如果出现RuntimeException,那么一定是程序员的错误。

检查异常 CheckedException

  比如I/O错误导致的IOException、SQLException。RuntimeException是那些可能在Java虚拟机正常运行期间抛出的异常的超类。
  一般是外部错误,这种异常都发生在编译阶段,Java编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行try catch,该类异常一般包括几个方面:

  1. 试图在文件尾部读取数据
  2. 试图打开一个错误格式的URL
  3. 试图根据给定的字符串查找class对象,而这个字符串表示的类并不存在

异常的处理方式

  1. 遇到问题不进行具体处理,而是继续抛给调用者(throw,throws),抛出异常有三种形式,一是throw,一个throws,还有一种系统自动抛异常。
  2. try catch捕获异常针对性处理方式

Throw和throws的区别:

  1. throws用在函数上,后面跟的是异常类,可以跟多个;而throw用在函数内,后面跟的是异常对象。
  2. throws用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方式;throw抛出具体的问题对象,执行到throw,功能就已经结束了,跳转到调用者,并将具体的问题对象抛给调用者。也就是说throw语句独立存在时,下面不要定义其他语句,因为执行不到。
  3. throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
  4. 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

java多线程(十二)

发表于 2020-04-13

AQS(抽象的队列同步器)

  AbstractQueuedSynchronizer类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。
  它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。
  state的访问方式有三种:getState(),setState(),compareAndSetState()。

AQS 定义两种资源共享方式

  1. Exclusive独占资源-ReentrantLock
    Exclusive(独占,只有一个线程能执行,如ReentrantLock)
  2. Share共享资源-Semaphore/CountDownLatch
    Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

  AQS只是一个框架,具体资源的获取/释放方式交由自定义同步器去实现,AQS这里只定义了一个接口,具体资源的获取交由自定义同步器去实现了(通过state的 get/set/CAS)之所以没有定义成abstract ,是因为独占模式下只用实现tryAcquire-tryRelease ,而共享模式下只用实现tryAcquireShared-tryReleaseShared。如果都定义成abstract,那么每个模式也要去实现另一模式下的接口。不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
  自定义同步器实现时主要实现以下几种方法:

  1. isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  2. tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  3. tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  4. tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  5. tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

同步器的实现是 ABS 核心(state 资源状态计数)

  同步器的实现是ABS核心,以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
  以CountDownLatch以例,任务分为N个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减 1。等到所有子线程都执行完后(即 state=0),会 unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

ReentrantReadWriteLock实现独占和共享两种方式

  一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquiretryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如 ReentrantReadWriteLock。

java多线程(十一)

发表于 2020-04-12

Java CAS 原理剖析

  在Java并发中,我们最初接触的应该就是synchronized关键字了,但是synchronized属于重量级锁,很多时候会引起性能问题,volatile也是个不错的选择,但是volatile不能保证原子性,只能在某些场合下使用。
  像synchronized这种独占锁属于悲观锁,它是在假设一定会发生冲突的,那么加锁恰好有用,除此之外,还有乐观锁,乐观锁的含义就是假设没有发生冲突,那么我正好可以进行某项操作,如果要是发生冲突呢,那我就重试直到成功,乐观锁最常见的就是CAS。
  我们在读Concurrent包下的类的源码时,发现无论是ReenterLock内部的AQS,还是各种Atomic开头的原子类,内部都应用到了CAS,最常见的就是我们在并发编程时遇到的i++这种情况。传统的方法肯定是在方法上加上synchronized关键字:

1
2
3
4
5
6
public class Test {
public volatile int i;
public synchronized void add() {
i++;
}
}

  但是这种方法在性能上可能会差一点,我们还可以使用AtomicInteger,就可以保证i原子的++了。

1
2
3
4
5
6
public class Test {
public AtomicInteger i;
public void add() {
i.getAndIncrement();
}
}

  我们来看getAndIncrement的内部:

1
2
3
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

  再深入到getAndAddInt():

1
2
3
4
5
6
7
8
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}

  这里我们见到compareAndSwapInt这个函数,它也是CAS缩写的由来。那么仔细分析下这个函数做了什么呢?
  首先我们发现compareAndSwapInt前面的this,那么它属于哪个类呢,我们看上一步getAndAddInt,前面是unsafe。这里我们进入的Unsafe类。这里要对Unsafe类做个说明。结合AtomicInteger的定义来说:

1
2
3
4
5
6
7
8
9
10
11
12
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;

  在AtomicInteger数据定义的部分,我们可以看到,其实实际存储的值是放在value中的,除此之外我们还获取了unsafe实例,并且定义了valueOffset。再看到static块,懂类加载过程的都知道,static块的加载发生于类加载的时候,是最先初始化的,这时候我们调用unsafe的objectFieldOffset从Atomic类文件中获取value的偏移量,那么valueOffset其实就是记录value的偏移量的。
  再回到上面一个函数getAndAddInt,我们看var5获取的是什么,通过调用unsafe的getIntVolatile(var1, var2),这是个native方法,具体实现到JDK源码里去看了,其实就是获取var1中,var2偏移量处的值。var1就是AtomicInteger,var2就是我们前面提到的valueOffset,这样我们就从内存里获取到现在valueOffset处的值了。
  现在重点来了,compareAndSwapInt(var1, var2, var5, var5 + var4)其实换成compareAndSwapInt(obj, offset, expect, update)比较清楚,意思就是如果obj内的value和expect相等,就证明没有其他线程改变过这个变量,那么就更新它为update,如果这一步的CAS没有成功,那就采用自旋的方式继续进行CAS操作,取出乍一看这也是两个步骤了啊,其实在JNI里是借助于一个CPU指令完成的。所以还是原子操作。

CAS 的问题

ABA问题

  CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。
  常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
  目前在JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

循环时间长开销大

  上面我们说过如果CAS不成功,则会原地自旋,如果长时间自旋会给CPU带来非常大的执行开销。

参考

https://juejin.im/post/5a73cbbff265da4e807783f5

1234…10
无恤

无恤

java博客

95 日志
7 分类
29 标签
GitHub
© 2018 - 2021 无恤
本站访客数:人次
本站总访问量次