Java学习记录

IDEA

JavaGuide IDEA 高效使用指南

改善体验的设置

  • Change font size with Command + Mouse Wheel:用鼠标滚轮改变字体大小
  • Sync setting across:选择设置同步的范围
    • IntelliJ IDEA instances only
    • All JetBrains IDE Products
  • File adn Code Templates:修改新建文件的默认模板(类,接口)
1
2
3
4
5
6
/**
* @author WYH
* @date ${YEAR}/${MONTH}/${DAY} ${HOUR}:${MINUTE}
**/
public class ${NAME} {
}
  • Live Templates:自定义快捷短语(比如sout)
  • File > New Projects Setup:设置新项目的Settings和Structure等

插件

  • CodeGlance Pro:显示当前文件代码概览

  • Github Copilot:AI

  • Key Promoter X:学习快捷键

  • Translation:翻译

  • Alibaba Java Code Guidelines:阿里巴巴代码规范

  • Rainbow Brackets:彩虹括号,清晰代码

  • Maven Helper:Maven依赖管理

  • Kafkalytic:Kafka管理

  • Zoolytic:ZooKeeper管理

快捷键

  • ctrl + H:查看当前类或接口的层次结构

  • ctrl + shift + B:查看方法或对象的实现

  • ctrl + E:查看最近的文件

  • ctrl + shift + E:查看最近的位置(窗口)

  • alt + home:使用导航

  • shift + esc:隐藏当前活跃窗口

  • 代码编辑

    • ctrl + shift + alt + T:打开重构框
  • alt + 7:查看Structure窗口

  • ctrl + alt + [/]:查看上一个/下一个 IDEA窗口

Maven

JavaGuide-Maven核心概念总结

JavaGuide-Maven使用技巧

Maven in 5 Minites官方上手教程

Java并发

Java多线程入门 类和接口

JDK提供了Thread类和Runnalble接口来让我们实现自己的“线程”类

Thread类

继承Thread类,并重写run方法

构造函数:

1
2
3
4
5
6
7
8
9
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals)
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}

Thread类常用方法

  1. currentThread():静态方法,返回对当前正在执行的线程对象的引用;
  2. start():开始执行线程的方法,java虚拟机会调用线程内的run()方法;
  3. yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;
  4. sleep():静态方法,使当前线程睡眠一段时间;
  5. join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;

Runnable接口

实现Runnable接口的run方法

Runnable是一个函数式接口,这意味着我们可以使用Java 8的函数式编程来简化代码

1
2
3
new Thread(() -> {
System.out.println("Java 8 匿名内部类");
}).start();

Thread类 对比 Runnable接口

  1. 由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
  2. Runnable接口出现更符合面向对象,将线程单独进行对象的封装
  3. Runnable接口出现,降低了线程对象和线程任务的耦合性
  4. 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量

乐观锁和悲观锁

参考JavaGuide 乐观锁和悲观锁详解

悲观锁:总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放

乐观锁:总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了

高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销。并且,悲观锁还可能会存在死锁问题,影响代码的正常运行。乐观锁相比悲观锁来说,不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹。不过,如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试,这样同样会非常影响性能,导致 CPU 飙升

乐观锁一般会使用版本号机制或 CAS 算法实现,CAS 算法相对来说更多一些,这里需要格外注意。CAS 的全称是 Compare And Swap(比较与交换) ,用于实现乐观锁,被广泛应用于各大框架中

CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新

  • CAS 算法的问题
    • ABA 问题:解决思路是在变量前面追加上版本号或者时间戳。JDK 1.5 以后的 AtomicStampedReference 类就是用来解决 ABA 问题的,其中的 compareAndSet() 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值
    • 循环时间长开销大:CAS 经常会用到自旋操作来进行重试,也就是不成功就一直循环执行直到成功。如果 JVM 能支持处理器提供的 pause 指令那么效率会有一定的提升
    • 只能保证一个共享变量的原子操作:从 JDK 1.5 开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作

Java内存模型

JavaGuide JMM详解

Redspider社区的《深入浅出Java多线程》

链接

简介:站在巨人的肩上,我们可以看得更远。本书内容的主要来源有博客、书籍、论文,对于一些已经叙述得很清晰的知识点我们直接引用在本书中;对于一些没有讲解清楚的知识点,我们加以画图或者编写Demo进行加工;而对于一些模棱两可的知识点,本书在查阅了大量资料的情况下,给出最合理的解释

Spring

mini-spring

mini-spring项目地址

视频教程

可拓展部分:只简单实现BeforeAdvice,有兴趣的同学可以帮忙实现另外几种Advice。定义MethodBeforeAdviceInterceptor拦截器,在执行被代理方法之前,先执行BeforeAdvice的方法。BeforeAdvice、AfterAdvice、AfterReturningAdvice、ThrowsAdvice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
└─springframework
├─aop # 切面模块,包括Advisor,Pointcut,MethodMatcher,TargetSource等
│ ├─aspectj # 具体的实现,包括AspectJExpressionPointcut和AspectJExpressionPointcutAdvisor
│ └─framework # 框架实现,包括动态代理部分:Jdk和Cglib,还有拦截器链部分:DefaultAdvisorChainFactory,以及代理类执行的proceed方法:ReflectiveMethodInvocation
│ ├─adapter # 包括MethodBeforeAdviceInterceptor和AfterReturningAdviceInterceptor
│ └─autoproxy # 包括DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware
├─beans # IOC模块
│ └─factory # IOC工厂
│ ├─annotation # 注解,包括Value注解和Autowired注解
│ ├─config # 包括Bean定义部分:BeanDefinition和BeanReference,还有Processor部分:BeanFactoryPostProcessor、BeanPostProcessor和InstantiationAwareBeanPostProcessor
│ ├─support # 包括DefaultListableBeanFactory类,以及这个类继承和实现的类和接口
│ └─xml # 读取xml文件的类
├─context # 上下文模块,包含ApplicationContext、ApplicationEvent和ApplicationListener等接口和类
│ ├─annotation # 包含Scope注解表示Bean是否是单例,包含ClassPathBeanDefinitionScanner类(doScan方法中注册BeanDefinition,处理@Autowired和@Value注解的BeanPostProcessor),包含ClassPathScanningCandidateComponentProvider类扫描Component注解
│ ├─event # 事件模块,包含ContextRefreshedEvent和ContextClosedEvent等类
│ └─support # 包含AbstractApplicationContext类(refresh方法),以及ClassPathXmlApplicationContext类(从xml文件加载的上下文)(继承自AbstractApplicationContext)等
├─core
│ ├─convert # 类型转换模块,使用环境:1. 在xml文件中Bean定义中 2. 在Value注解中。例如将"1000"字符串转换为数字1000
│ │ ├─converter
│ │ └─support
│ └─io # 资源读取模块,包括ClassPath、FileSystem和Url
├─stereotype # Component注解
└─util # 仅包含StringValueResolver类

RPC

JavaGuide的《手写 RPC 框架》

一款基于 Netty+Kyro+Zookeeper 实现的 RPC 框架

guide-rpc-framework

guide-rpc-framework-learning

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
├─example-client:示例客户端
├─example-server:示例服务端
├─hello-service-api:示例服务
├─rpc-framework-common:rpc框架通用组件
│ ├─src
│ │ └─main
│ │ └─java
│ │ └─github
│ │ └─javaguide
│ │ ├─enums:常量
│ │ ├─exception:自定义异常
│ │ ├─extension:dubbo spi(服务发现机制)
│ │ ├─factory:获取单例对象的工厂类
│ │ └─utils:工具集合
│ │ └─concurrent:并行工具
│ │ └─threadpool:线程池工具
└─rpc-framework-simple
├─src
│ ├─main
│ │ ├─java
│ │ │ └─github
│ │ │ └─javaguide
│ │ │ ├─annotation:自定义注解
│ │ │ │ RpcReference.java:用于注解类的实例
│ │ │ │ RpcScan.java:自定义的扫描注解
│ │ │ │ RpcService.java:用于注解类的实现
│ │ │ ├─compress:压缩工具
│ │ │ ├─config
│ │ │ │ CustomShutdownHook.java:服务关闭后的操作
│ │ │ │ RpcServiceConfig.java:Rpc服务的配置类
│ │ │ ├─loadbalance:负载均衡
│ │ │ ├─provider:服务提供类
│ │ │ ├─proxy:服务代理
│ │ │ ├─registry:服务发现和仓库
│ │ │ │ │ ServiceDiscovery.java
│ │ │ │ │ ServiceRegistry.java
│ │ │ │ └─zk:Zookeeper实现
│ │ │ │ │ ZkServiceDiscoveryImpl.java
│ │ │ │ │ ZkServiceRegistryImpl.java
│ │ │ │ └─util
│ │ │ │ CuratorUtils.java:Zookeeper客户端
│ │ │ ├─remoting
│ │ │ │ ├─constants:常量
│ │ │ │ ├─dto:传输消息实体
│ │ │ │ │ RpcMessage.java:消息
│ │ │ │ │ RpcRequest.java:请求
│ │ │ │ │ RpcResponse.java:回应
│ │ │ │ ├─handler:请求处理类
│ │ │ │ └─transport:传输
│ │ │ │ │ RpcRequestTransport.java:传输接口
│ │ │ │ ├─netty:Netty实现
│ │ │ │ │ ├─client:客户端
│ │ │ │ │ ├─codec:编解码器
│ │ │ │ │ └─server:服务端
│ │ │ │ └─socket:Java原生 Socket实现
│ │ │ │ SocketRpcClient.java:客户端
│ │ │ │ SocketRpcRequestHandlerRunnable.java:请求处理
│ │ │ │ SocketRpcServer.java:服务端
│ │ │ ├─serialize:序列化(三种实现)
│ │ │ └─spring
│ │ │ CustomScanner.java:继承ClassPathBeanDefinitionScanner
│ │ │ CustomScannerRegistrar.java:RpcScan注解用到的扫描类
│ │ │ SpringBeanPostProcessor.java:Bean定义前后的处理
│ │ └─resources
│ │ └─META-INF
│ │ └─extensions:dubbo spi使用的资源

dubbo spi:SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。

MISC

类和对象

equals与hashcode

  • equals方法
    • 比较两个对象值是否相等
    • 默认(Objects)与==比较相同,即比较对象地址是否相同
    • 重写equals方法:
      • 例如,String类就重写equals方法,比较的是字符串内容
  • hashcode方法
    • 每个对象都有一个hashcode,默认是对象地址,是一个int值,用于在哈希结构中散列到某个位置
    • 对于hashmap来说,如果两个对象相等,那么它们的hashCode值一定要相同,否则在哈希表的不同位置会出现相等的对象
    • 例如,在hashmap中,索引对象时先通过hashcode找到位置,再通过equals方法比较对象的值是否相同,如果相同,则不再放入hashmap;如果不同,则添加到链尾
  • 重写equals必须重写hashcode
    • 确保equals时的hashcode能覆盖!
    • 重写equals前:因为hashcode的值默认是对象地址,不同的对象它们的地址不同,hashcode就不同,equals默认比较的也是地址,所以equals的结果是false
    • 重写equals后:不同对象它们equals结果则可能为true,而在hashmap中,存入这两个对象(通过hashcode),会存到不同的位置,这就导致了hashmap中出现了两个值相同的对象,违背了hashmap的原则。所以,需要重写hashcode方法,使值相同的对象的hashcode值也必须相同。

Stream