Java基础知识

TOC

[TOC]

目标

以面试为导向,知识点要全面,查漏补缺。突出重点。

学完后有一个基本的认知是什么,对于重点内容要知道为什么,了解实现原理。

基础

强引用、弱引用、虚引用、软引用

参见Java中的四种引用类型

背景

简单了解即可。和引用关联的知识点,不得不提对象的生命周期,再远点就是GC如何管理对象的生命周期的。这里假设读者已经知道这些内容了。

引用强度认知:

引用强度从强到弱分别为:强引用、软引用、弱引用、虚引用。

强引用 StrongReference

默认引用形式,使用时不需要显示定义。任何通过强引用所使用的对象不管系统资源有多紧张,Java GC都不会主动回收具有强引用的对象。

弱引用 WeakReference

如果一个对象只具有弱引用,无论内存充足与否,Java GC后对象如果只有弱引用将会被自动回收。

软引用 SoftReference

软引用和弱引用的特性基本一致, 主要的区别在于软引用在内存不足时才会被回收。如果一个对象只具有软引用,Java GC在内存充足的时候不会回收它,内存不足时才会被回收。

虚引用 PhantomReference

从PhantomReference类的源代码可以知道,它的get()方法无论何时返回的都只会是null。所以单独使用虚引用时,没有什么意义,需要和引用队列ReferenceQueue类联合使用。当执行Java GC时如果一个对象只有虚引用,就会把这个对象加入到与之关联的ReferenceQueue中。

文字干巴巴,去看原文代码

小结

强引用是 Java 的默认引用形式,使用时不需要显示定义,是我们平时最常使用到的引用方式。不管系统资源有多紧张,Java GC都不会主动回收具有强引用的对象。
  弱引用和软引用一般在引用对象为非必需对象的时候使用。它们的区别是被弱引用关联的对象在垃圾回收时总是会被回收,被软引用关联的对象只有在内存不足时才会被回收。
  虚引用的get()方法获取的永远是null,无法获取对象实例。Java GC会把虚引用的对象放到引用队列里面。可用来在对象被回收时做额外的一些资源清理或事物回滚等处理。
  由于无法从虚引获取到引用对象的实例。它的使用情况比较特别,所以这里不把虚引用放入表格进行对比。这里对强引用、弱引用、软引用进行对比:

final关键字的作用 (方法、变量、类)

参见final 关键字

应用在方法、变量、类上的作用不赘述,已经掌握,只查漏补缺

  • 被final修饰的方法,不可以被重写。但是不影响本类的重载以及重载函数的重写。

  • final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。

不可变的类是天然无状态的,线程安全的。

如何写一个不可变类呢?

  • 将类声明为final,所以它不能被继承
  • 将所有的成员声明为私有的,这样就不允许直接访问这些成员
  • 对变量不要提供setter方法
  • 将所有可变的成员声明为final,这样只能对它们赋值一次
  • 通过构造器初始化所有成员,进行深拷贝(deep copy)
  • 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝

具体可参考String和Integer等的写法

和static一起使用

对于静态final变量,我们可以直接初始化,或者使用静态代码块。而不可以使用构造函数或者构造代码块。

因为static要求在编译期间就确定值,然后放入静态区。而构造函数和构造代码块发生在运行期间。

final和private

类中所有的private方法都隐式的指定为final的,由于无法取用private方法,所以也就无法覆盖它,可以对private方法添加final修饰符,但并没有添加任何额外意义。(了解即可)

泛型、泛型继承、泛型擦除

泛型擦除分析
泛型擦除是什么,会带来什么问题?

泛型擦除

使用泛型的话,运行期把对象都是当成object来处理的,所以可以运用的方法都是object的方法,且在赋值操作时,编译器会自动强转为指定泛型类型,另一个好处就是在编译期更早的发现向下转型可能出现的错误,因为向下转型是不安全的.

带来什么样的问题?

在运行时反编译添加不同类型的数据,数据错误,代码不报错。

泛型继承,带上界的擦除

当使用上界时泛型擦除擦除为上界的类型,因此也就解释了为啥可以调用上界的方法.并且会和赋值操作的时候一样自动强转为对应的泛型,向上转型,为安全的操作。

带通配符的上界

带通配符的下界

总结

泛型的出现是为了减少向下转型出现的错误,泛型的目的是尽可能的在编译器发现转型时的错误,所以对于不安全的操作(编译器认为的)会绝对禁止,存储进去的都是绝对安全(编译器认为的)的数据.

jdk ServiceLoader

参见Java SPI机制:ServiceLoader实现原理及应用剖析

SPI,全称Service Provider Interfaces,服务提供接口。可以用来解耦服务的实现和使用,增强应用的可扩展性

JDK中,基于SPI的思想,提供了默认具体的实现,ServiceLoader。利用JDK自带的ServiceLoader,可以轻松实现面向服务的注册与发现,完成服务提供与使用的解耦

使用

外部使用时,往往通过load(Class service, ClassLoader loader)load(Class service)调用,最后都是在reload方法中创建了LazyIterator对象,LazyIteratorServiceLoader的内部类,实现了Iterator接口,其作用是一个懒加载的迭代器,在hasNextService方法中,完成了对位于META-INF/services/目录下的配置文件的解析,并在nextService方法中,完成了对具体实现类的实例化。

META-INF/services/,是ServiceLoader中约定的接口与实现类的关系配置目录,文件名是接口全限定类名,内容是接口对应的具体实现类,如果有多个实现类,分别将不同的实现类都分别作为每一行去配置。解析过程中,通过LinkedHashMap<String,S>数据结构的providers,将已经发现了的接口实现类进行了缓存,并对外提供的iterator()方法,方便外部遍历。

总结

基于服务提供与发现的思想,系统自带的ServiceLoader以及基于此思想基础上的演化形式,被广泛的使用到实际的项目中。本质上,通过服务接口约定、服务注册与服务发现,完成将服务提供方与服务使用方的解耦,大大扩展了系统的可扩展性服务注册的本质,是将服务接口与具体服务实现的映射关系注册到系统或特定实现中。服务发现的过程,本质上是向系统或特定实现去匹配对应的具体实现类,但在写法上是基于接口的编程方式,因为服务使用方和服务提供方彼此都是透明与未感知的。基于SPI思想的ServiceLoader实现及演化,在项目的组件化,或实现扩展性功能,甚至完成具有可插拔能力的插件化模块时,往往都被广泛使用到。

LinkedList、LinkedHashMap、LRU

LinkedList

底层数据结构

设计模式

装饰者模式

代理模式

责任链模式

工厂模式

适配器模式

建造者模式

单例模式

模板模式

观察者模式

关于精度损失问题:int、long 超过最大值

关于注解:元注解的种类、继承java.lang.Annotation、注解的基础类型、注解的常用方法

关于ClassLoader,类加载器,双亲委派模型

Spring Boot

如何自定义starter

  1. 引入spring-boot-autoconfigure依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-autoconfigure</artifactId>
	<version>2.2.2.RELEASE</version>
</dependency>
  1. 编写JavaBean
@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {

    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "SimpleBean{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
  1. 编写配置类
@Configuration
@ConditionalOnClass//当类路径下有指定当类自动配置
public class MyAutoConfiguration {

    static {
        System.out.println("MyAutoConfiguration init....");
    }

    @Bean
    public SimpleBean simpleBean() {
        return new SimpleBean();
    }
}
  1. 在resources下创建/META-INF/spring.fatories文件

在该文件中配置自己的自动配置类,多个配置用逗号隔开

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lagou.config.MyAutoConfiguration

Spring Boot启动流程

@SpringBootApplication

@SpringBootApplication注解里定义了3个事情:

@SpringBootConfiguration声明使用该注解的类是配置类,由Spring IOC容器托管。

@EnableAutoConfiguration声明开启自动配置。

@ComponentScan声明包扫描的范围。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@SpringBootConfiguration

@SpringBootConfiguration注解内部有一个核心注解@Configuration,表示当前类是一个配置类,并可以被组建扫描器扫描。

@EnableAutoConfiguration

@EnableAutoConfiguration主要干了两件事:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

@AutoConfigurationPackage自动配置包

@Import(AutoConfigurationImportSelector.class)自动配置类扫描导入

源码剖析

自动配置入口AbstractApplicationContext.invokeBeanFactoryPostProcessors

自动配置关键类方法AutoConfigurationImportSelector.getAutoConfigurationEntry

具体细节:

getCandidateConfigurations方法中调用SpringFactoriesLoader.loadFactoryNames

从类路径下的META-INF/spring.factories文件中加载自动配置类

小结

@EnableAutoConfiguration就是从classpath中搜寻META-INF/spring.factories配置文件,并将其中的org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置项通过反射实例化对应的标注了@Configuration的JavaConfig形式的配置类,并加载到IOC容器中。

@ComponentScan注解

扫描启动类所在包及其子包下的类,扫描过程中由@AutoConfigurationPackage注解进行解析,从而得到启动类所在包的具体位置。

总结:

@SpringBootApplication注解主要是3个注解的组合注解。

image-20200423142226336

执行原理

SpringApplication实例的初始化创建

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

项目的初始化启动

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
  //1 获取并启动监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //2 准备环境
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
      //3 创建Spring容器
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
      //4 Spring容器前置处理
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 5 刷新容器
			refreshContext(context);
      //6 容器后置处理
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
      //7 发出结束执行的事件
			listeners.started(context);
      //返回容器
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
image-20200423143258403

Spring

IOC

什么是IOC

控制反转,管理对象的生命周期及对象之间的依赖关系

对象的创建,new的方式,改为从容器中拿。

构造器注入和set方法注入。

什么是对象的生命周期

image-20200423153452549

实例化bean,设置属性值,调用BeanPostProcessor的初始化前方法,调用初始化方法,调用BeanPostProcessor的初始化后方法。

延迟加载

被修饰为lazy-init的bean ,Spring容器初始化阶段不会进行初始化,第一次进行getBean调用时,才初始化调用

如何处理循环依赖

用了三级缓存。

  • 单例bean构造器参数循环依赖(无法解决)

  • prototype原型bean循环依赖(无法解决)

  • 单例bean通过set注入或@Autowired循环依赖(可以解决)

原理:基于Java引用传递,当获得对象的引用时,对象的属性是可以延迟后设置的,但是构造器必须在获取引用之前。

通过提前暴露一个ObjectFactory对象来完成,简单来说,ClassA在调用构造器完成对象初始化之后,在调用ClassA的serClassB方法之前,就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。

循环依赖问题

AOP

面向切面编程

在不改变原有业务逻辑的基础上,增强横切逻辑,根本上解耦合,避免横切逻辑代码重复。

事务

本质,是对Connection的管理。将连接绑定到当前线程,从当前线程获取连接,运用ThreadLocal。

DataSourceTransactionManager

@Override
	protected Object doGetTransaction() {
		DataSourceTransactionObject txObject = new DataSourceTransactionObject();
		txObject.setSavepointAllowed(isNestedTransactionAllowed());
    //从ThreadLoacal拿连接
		ConnectionHolder conHolder =
				(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
		txObject.setConnectionHolder(conHolder, false);
		return txObject;
	}
@Nullable
	private static Object doGetResource(Object actualKey) {
		Map<Object, Object> map = resources.get();//看resources这个变量
		if (map == null) {
			return null;
		}
		Object value = map.get(actualKey);
		// Transparently remove ResourceHolder that was marked as void...
		if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
			map.remove(actualKey);
			// Remove entire ThreadLocal if empty...
			if (map.isEmpty()) {
				resources.remove();
			}
			value = null;
		}
		return value;
	}

看一下resources变量在TransactionSynchronizationManager中的定义

private static final ThreadLocal<Map<Object, Object>> resources =
      new NamedThreadLocal<>("Transactional resources");

Spring事务管理基于AOP,如果需要增强Bean。使用AOP添加事务支持。

AOP是基于Spring扩展点BeanPostProcessor.postProcessAfterInitailization

同一个类中非事务方法调用@Transaction注解的事务方法事务失效问题

@Service
class A{
    @Transactinal
    method b(){...}
    
    method a(){    //标记1
        b();
    }
}
 
//Spring扫描注解后,创建了另外一个代理类,并为有注解的方法插入一个startTransaction()方法:
class proxy$A{
    A objectA = new A();
    method b(){    //标记2
        startTransaction();
        objectA.b();
    }
 
    method a(){    //标记3
        objectA.a();    //由于a()没有注解,所以不会启动transaction,而是直接调用A的实例的a()方法
    }
}

当我们调用A的bean的a()方法的时候,也是被proxyAproxyA拦截,执行proxyA.a()(标记3),然而,由以上代码可知,这时候它调用的是objectA.a(),也就是由原来的bean来调用a()方法了,所以代码跑到了“标记1”。由此可见,“标记2”并没有被执行到,所以startTransaction()方法也没有运行。

https://zhuanlan.zhihu.com/p/101396825

https://mp.weixin.qq.com/s/1TEBnmWynN4nwc6Q-oZfvw

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

https://blog.csdn.net/levae1024/article/details/82998386

https://blog.csdn.net/m0_38027656/article/details/84190949?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2

解决方法:

springboot启动类加上注解:@EnableAspectJAutoProxy(exposeProxy = true)

使用AopContext.currentProxy()获取代理对象

Redis

持久化方式

AOF,RDB

优缺点

健过期策略

集群方案

Codis

分布式事务

2PC

结合案例,应用存储数据,然后投递到MQ,如何保障事务。

开启事务,协调者向所有的事务参与者发送事务内容

1.数据存储失败,应用服务自己回滚。

2.数据存储成功,消息投递失败,消息没有发送成功,抛出了异常,定时重新发。

3.数据存储成功,消息投递失败,消息发送成功,消费端自己挂了,没有接收到。

这种情况,如果需要保证客户端收到,mq需要支持收到确认,只有消费者收到并发送确认到ack给mq,mq才知道消费者收到,否则,mq自己重发。

如果消息是可丢失的,不重要,那么就不需要客户端确认,不用管消费端挂了,在不在线。

Time waits for no one.