如需下载整套资料。链接放在最后了啦!
replace方法:支持字符和字符串的替换。
public String replace(char oldChar, char newChar)
public String replace(CharSequence target, CharSequence replacement)
replaceAll方法:基于正则表达式的字符串替换。
public String replaceAll(String regex, String replacement)
测试代码:
String str = "Hello Java. Java is a language.";
System.out.println(str.replace("Java.", "c++"));//打印 Hello c++ Java is a language.
System.out.println(str.replaceAll("Java.", "c++"));//打印 Hello c++ c++is a language.
打印结果:
Hello c++ Java is a language.
Hello c++ c++is a language.
答案:C
分析:
Java 中,常用的对字符串操作的类有 String、StringBuffer、StringBuilder
线程的安全性问题体现在:
导致原因:
解决办法:
网络延迟时重复点击提交按钮,会发生重复提交表单的问题。
解决办法:
官方 FAQ 上说明,理论上 List、Set、Sorted Set 可以放 2 的 32 次方个元素
Parents Delegation Model,这里的 Parents 翻译成双亲有点不妥,类加载向上传递的过程中只有单亲;parents 更多的是多级向上的意思。
除了顶层的启动类加载器,其他的类加载器在加载之前,都会委派给它的父加载器进行加载,一层层向上传递,直到所有父类加载器都无法加载,自己才会加载该类。
双亲委派模型,更好地解决了各个类加载器协作时基础类的一致性问题,避免类的重复加载;防止核心API库被随意篡改。
JDK 9 之前
JDK 9 开始 Extension ClassLoader 被 Platform ClassLoader 取代,启动类加载器、平台类加载器、应用程序类加载器全都继承于 jdk.internal.loader.BuiltinClassLoader
类加载代码逻辑
protected synchronized Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 首先,检查请求的类是否已经被加载过了
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器抛出ClassNotFoundException
// 说明父类加载器无法完成加载请求
}
if (c == null) {
// 在父类加载器无法加载时
// 再调用本身的findClass方法来进行类加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
基本数据类型
补充说明:字节的英文是 byte,位的英文是 bit
详细说明可以参考:
可重入锁
指在同一个线程在外层方法获取锁的时候,进入内层方法会自动获取锁。
为了避免死锁的发生,JDK 中基本都是可重入锁。
下面我们来测试一下 synchronized 和 java.util.concurrent.lock.ReentrantLock 锁的可重入性
测试 synchronized 加锁 可重入性
package constxiong.concurrency.a019;
/**
测试 synchronized 加锁 可重入性
@author ConstXiong
@date 2019-09-20 15:55:27
*/
public class TestSynchronizedReentrant {
public static void main(String[] args) {
new Thread(new SynchronizedReentrant()).start();
}
}
class SynchronizedReentrant implements Runnable {
private final Object obj = new Object();
/**
* 方法1,调用方法2
*/
public void method1() {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " method1()");
method2();
}
}
/**
* 方法2,打印前获取 obj 锁
* 如果同一线程,锁不可重入的话,method2 需要等待 method1 释放 obj 锁
*/
public void method2() {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " method2()");
}
}
@Override
public void run() {
//线程启动 执行方法1
method1();
}
}
打印结果:
Thread-0 method1()
Thread-0 method2()
测试 ReentrantLock 的可重入性
package constxiong.concurrency.a019;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
测试 ReentrantLock 的可重入性
@author ConstXiong
@date 2019-09-20 16:24:52
*/
public class TestLockReentrant {
public static void main(String[] args) {
new Thread(new LockReentrant()).start();
}
}
class LockReentrant implements Runnable {
private final Lock lock = new ReentrantLock();
/**
* 方法1,调用方法2
*/
public void method1() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " method1()");
method2();
} finally {
lock.unlock();
}
}
/**
* 方法2,打印前获取 obj 锁
* 如果同一线程,锁不可重入的话,method2 需要等待 method1 释放 obj 锁
*/
public void method2() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " method2()");
} finally {
lock.unlock();
}
}
@Override
public void run() {
//线程启动 执行方法1
method1();
}
}
打印结果:
Thread-0 method1()
Thread-0 method2()
测试不可重入锁
我在 JDK 中没找到不可重入锁,所以考虑自己实现一下。两种方式:通过 synchronized wait notify 实现;通过 CAS + 自旋方式实现
1) synchronized wait notify 方式实现
package constxiong.concurrency.a019;
/**
* 不可重入锁,通过 synchronized wait notify 实现
* @author ConstXiong
* @date 2019-09-20 16:53:34
*/
public class NonReentrantLockByWait {
//是否被锁
private volatile boolean locked = false;
//加锁
public synchronized void lock() {
//当某个线程获取锁成功,其他线程进入等待状态
while (locked) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//加锁成功,locked 设置为 true
locked = true;
}
//释放锁
public synchronized void unlock() {
locked = false;
notify();
}
}
2) 通过 CAS + 自旋 方式实现
package constxiong.concurrency.a019;
import java.util.concurrent.atomic.AtomicReference;
/**
* 不可重入锁,通过 CAS + 自旋 实现
* @author ConstXiong
* @date 2019-09-20 16:53:34
*/
public class NonReentrantLockByCAS {
private AtomicReference lockedThread = new AtomicReference();
public void lock() {
Thread t = Thread.currentThread();
//当 lockedThread 持有引用变量为 null 时,设置 lockedThread 持有引用为 当前线程变量
while (!lockedThread.compareAndSet(null, t)) {
//自旋,空循环,等到锁被释放
}
}
public void unlock() {
//如果是本线程锁定的,可以成功释放锁
lockedThread.compareAndSet(Thread.currentThread(), null);
}
}
测试类
package constxiong.concurrency.a019;
/**
* 测试不可重入锁
* @author ConstXiong
* @date 2019-09-20 18:08:55
*/
public class TestLockNonReentrant{
public static void main(String[] args) {
new Thread(new LockNonReentrant()).start();
}
}
class LockNonReentrant implements Runnable {
// private final NonReentrantLockByWait lock = new NonReentrantLockByWait();
private final NonReentrantLockByCAS lock = new NonReentrantLockByCAS();
/**
* 方法1,调用方法2
*/
public void method1() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " method1()");
method2();
} finally {
lock.unlock();
}
}
/**
* 方法2,打印前获取 obj 锁
* 如果同一线程,锁不可重入的话,method2 需要等待 method1 释放 obj 锁
*/
public void method2() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " method2()");
} finally {
lock.unlock();
}
}
@Override
public void run() {
//线程启动 执行方法1
method1();
}
}
测试结果,都是在 method1,调用 method2 的时候,导致了死锁,线程一直等待或者自旋下去。
参考:
一、字面量的形式
var expression = /pattern/flags;
flags参数
i:忽略大小写
g:全局匹配
gi:全局匹配+忽略大小写
二、使用 RegExp 对象
var reg = new RegExp(expression, destStr);
RegExp.$1 是 RegExp 的一个属性,指的是与正则表达式匹配的第一个子匹配(以括号为标志)字符串。以此类推,RegExp.2, RegExp.3, ..RegExp.$99总共可以有99个匹配
test()方法:测试正则是否匹配字符串
正则.test(字符串)
如:
/^\d/.test(‘1a’)
new RegExp(“1a”, ‘i’).test(‘1a1a’)
search()方法:在字符串搜索符合正则的内容,搜索到就返回出现的位置,搜索失败就返回 -1
字符串.search(正则)
如:
‘1a1a’.search(/^\d/); //返回0
match()方法:
stringObject.match(searchvalue)
stringObject.match(regexp)
如:
‘1a1a’.match(/^\d/); //返回[“1”, index: 0, input: “1a1a”, groups: undefined]
replace()方法:
replace([RegExp|String],[String|Function])
如:
‘1a1a’.replace(/^\d/, 2); //返回"2a1a"
exec()方法:捕获组,仅 RegExp 对象可用
如:
ar r = new RegExp(“(1a)”, ‘i’); r.exec(‘1a1a’); RegExp.$1;
1、概念
SQL 注入(SQL Injection),是 Web 开发中最常见的一种安全漏洞。
可以用它来从数据库获取敏感信息、利用数据库的特性执行添加用户、导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限。
**2、造成 SQL 注入的原因 **
程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的 SQL 脚本,程序在接收后错误的将攻击者的输入作为 SQL 语句的一部分执行,导致原始的查询逻辑被改变,执行了攻击者精心构造的恶意 SQL 语句。
如 从用户表根据用户名 ConstXiong 和密码 123 查用户信息
select * from user where username = 'ConstXiong' and password = '123'
恶意修改用户名参数 ConstXiong -> ConstXiong’ or 1=1 –
select * from user where username = 'ConstXiong' or 1=1 --' and password = '123'
![Image 1][]![Image 1][]
SQL 中 – 是注释标记,如果上面这个 SQL 被执行,就可以让攻击者在不知道任何用户名和密码的情况下成功登录。
3、预防 SQL 注入攻击的方法
[Image 1]:
触发器是指一段代码,当触发某个事件时,自动执行这些代码
MySQL 数据库中有六种触发器:
使用场景:
注意:滥用会造成数据库及应用程序的维护困难
Java 中 happens-before 原则,是在 JSR-133 中提出的。
原文摘要:
• Each action in a thread happens-before every subsequent action in that thread.
• An unlock on a monitor happens-before every subsequent lock on that monitor.
• A write to a volatile field happens-before every subsequent read of that volatile.
• A call to start() on a thread happens-before any actions in the started thread.
• All actions in a thread happen-before any other thread successfully returns from a join() on that thread.
• If an action a happens-before an action b, and b happens before an action c, then a happensbefore c.
• the completion of an object’s constructor happens-before the execution of its finalize method (in the formal sense of happens-before).
翻译过来加上自己的理解就是:
Mybatis 的 XML 配置中,在 节点中添加子节点 ,name=defaultStatementTimeout,设置等待数据库响应的超时时间。
Redis 是一款使用 C 语言编写的高性能 key-value 数据库,开源免费,遵守 BSD 协议。
特点:
100 = 1*(8*8) + 4*(8) + 4*(1)
八进制:144
Java中八进制数必须以0开头,0144
方式一、 标签使用 resultType 参数,传递 Java 类,sql 中 select 的字段名保持与 Java 类属性名称一致
方式二、使用 标签,定义数据库列名和对象属性名之间的映射关系
方式三、使用注解 select 的字段名保持与接口方法返回的 Java 类或集合的元素类的属性名称一致
方式一
select * from user where id = #{id}
方式二
select * from user where id = #{id}
方式三
@Select(“select * from user”)
List selectAllUsers();
根据解析得到 ResultMap 结合 sql 执行结果,通过反射创建对象,根据映射关系反射填充返回对象的属性
源码体现在 DefaultResultSetHandler 的 handleResultSets 方法
public List
加锁顺序不一致可能会导致死锁:
1、事务 1 持有 id = 1 的行锁,更新 id = 2 的行数据;事务 2 持有 id = 2 的行锁,更新 id = 1的行数据
2、在范围查询更新时,加锁是一条记录一条记录挨个加锁的,数据行被加锁顺序不一样也会导致死锁
事务1
update table set name = 'A' where id <100;
事务2
update table set name = 'B' where age > 25;
都是非线程安全,允许存放 null
测试代码
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
LinkedList linkedList = new LinkedList();
int size = 10000 * 1000;
int index = 5000 * 1000;
System.out.println("arrayList add " + size);
addData(arrayList, size);
System.out.println("linkedList add " + + size);
addData(linkedList, size);
System.out.println();
System.out.println("arrayList get " + index + " th");
getIndex(arrayList, index);
System.out.println("linkedList get " + index + " th");
getIndex(linkedList, index);
System.out.println();
System.out.println("arrayList set " + index + " th");
setIndex(arrayList, index);
System.out.println("linkedList set " + index + " th");
setIndex(linkedList, index);
System.out.println();
System.out.println("arrayList add " + index + " th");
addIndex(arrayList, index);
System.out.println("linkedList add " + index + " th");
addIndex(linkedList, index);
System.out.println();
System.out.println("arrayList remove " + index + " th");
removeIndex(arrayList, index);
System.out.println("linkedList remove " + index + " th");
removeIndex(linkedList, index);
System.out.println();
System.out.println("arrayList remove Object " + index);
removeObject(arrayList, (Object)index);
System.out.println("linkedList remove Object " + index);
removeObject(linkedList, (Object)index);
System.out.println();
System.out.println("arrayList add");
add(arrayList);
System.out.println("linkedList add");
add(linkedList);
System.out.println();
System.out.println("arrayList foreach");
foreach(arrayList);
System.out.println("linkedList foreach");
foreach(linkedList);
System.out.println();
System.out.println("arrayList forSize");
forSize(arrayList);
System.out.println("linkedList forSize");
// forSize(linkedList);
System.out.println("cost time: ...");
System.out.println();
System.out.println("arrayList iterator");
ite(arrayList);
System.out.println("linkedList iterator");
ite(linkedList);
}
private static void addData(List list, int size) {
long s1 = System.currentTimeMillis();
for (int i = 0; i list, int index) {
long s1 = System.currentTimeMillis();
list.get(index);
long s2 = System.currentTimeMillis();
System.out.println("cost time: " + (s2-s1));
}
private static void setIndex(List list, int index) {
long s1 = System.currentTimeMillis();
list.set(index, 1024);
long s2 = System.currentTimeMillis();
System.out.println("cost time: " + (s2-s1));
}
private static void addIndex(List list, int index) {
long s1 = System.currentTimeMillis();
list.add(index, 1024);
long s2 = System.currentTimeMillis();
System.out.println("cost time: " + (s2-s1));
}
private static void removeIndex(List list, int index) {
long s1 = System.currentTimeMillis();
list.remove(index);
long s2 = System.currentTimeMillis();
System.out.println("cost time: " + (s2-s1));
}
private static void removeObject(List list, Object obj) {
long s1 = System.currentTimeMillis();
list.remove(obj);
long s2 = System.currentTimeMillis();
System.out.println("cost time: " + (s2-s1));
}
private static void add(List list) {
long s1 = System.currentTimeMillis();
list.add(1024);
long s2 = System.currentTimeMillis();
System.out.println("cost time: " + (s2-s1));
}
private static void foreach(List list) {
long s1 = System.currentTimeMillis();
for (Integer i : list) {
//do nothing
}
long s2 = System.currentTimeMillis();
System.out.println("cost time: " + (s2-s1));
}
private static void forSize(List list) {
long s1 = System.currentTimeMillis();
int size = list.size();
for (int i = 0; i list) {
long s1 = System.currentTimeMillis();
Iterator ite = list.iterator();
while (ite.hasNext()) {
ite.next();
}
long s2 = System.currentTimeMillis();
System.out.println("cost time: " + (s2-s1));
}
JDK1.8,win7 64位。结果
arrayList add 10000000
cost time: 3309
linkedList add 10000000
cost time: 1375
arrayList get 5000000 th
cost time: 0
linkedList get 5000000 th
cost time: 53
arrayList set 5000000 th
cost time: 0
linkedList set 5000000 th
cost time: 44
arrayList add 5000000 th
cost time: 3
linkedList add 5000000 th
cost time: 45
arrayList remove 5000000 th
cost time: 3
linkedList remove 5000000 th
cost time: 46
arrayList remove Object 5000000
cost time: 31
linkedList remove Object 5000000
cost time: 131
arrayList add
cost time: 0
linkedList add
cost time: 0
arrayList foreach
cost time: 30
linkedList foreach
cost time: 128
arrayList forSize
cost time: 5
linkedList forSize
cost time: ...
arrayList iterator
cost time: 6
linkedList iterator
cost time: 113
思考:
源码分析参考:
创建型
结构型
行为型
详细可以参考:
答案:A
分析:
Dubbo 可以在提供端(provider) 和 消费端(consumer) 设置超时间
provider:
系统向外提供的 facade 请求超时时间,默认1000 ms
provider 接受到请求时,会把整个处理逻辑执行完,不管你是否设置了时间;dubbo 只会在方法执行完,判断是否超时,如果超时,记一个 warn 日志
consumer:
调用外部系统接口的超时时间,默认1000 ms
请求发出后,线程处于阻塞状态,线程的唤醒条件是超时和收到 provider 返回
provider 和 consumer 都设置了超时时间,Dubbo 会默认优先使用 consumer 的配置
官方文档:
- 方法级优先,接口级次之,全局配置再次之
- 如果级别一样,则消费方优先,提供方次之
- 其中,服务提供方配置,通过 URL 经由注册中心传递给消费方。
- 建议由服务提供方设置超时,因为一个方法需要执行多长时间,服务提供方更清楚,如果一个消费方同时引用多个服务,就不需要关心每个服务的超时设置
框架如何实现,这个问题的细节就特别多了,画了一张我个人理解的图
不区分,下面 sql 都是可以的,如:
前面了解了 冒泡排序 和 插入排序,时间复杂度、空间复杂度都相同:
但为什么实际开发中插入排序使用偏多呢?
原因如下:
代码对比:
//冒泡排序
int temp = array[j + 1];
array[j+1] = array[j];
array[j] = temp;
hasSwitch = true;//有数据交换
//插入排序
if (array[j] > value) {
array[j+1] = array[j];
} else {
break;
}
测试代码:
package constxiong.interview.algorithm;
import java.util.Random;
/**
* 测试冒泡排序
* @author ConstXiong
* @date 2020-04-10 09:36:54
*/
public class CompareBubbleAndInsertionSort {
public static void main(String[] args) {
//生成两个一样长度的随机数组
int length = 10000;
int[] array_1 = generateArray(length);
int[] array_2 = new int[length];
System.arraycopy(array_1, 0, array_2, 0, length);
print(array_1);
print(array_2);
//比较冒泡排序与插入排序的耗时
long array_1_start = System.currentTimeMillis();
bubbleSort(array_1);
System.out.println("bubbleSort cost time : " + (System.currentTimeMillis() - array_1_start));
long array_2_start = System.currentTimeMillis();
insertionSort(array_2);
System.out.println("insertionSort cost time : " + (System.currentTimeMillis() - array_2_start));
//打印排序后的两个数组,看看结果是否正确
print(array_1);
print(array_2);
}
/**
* 生成随机数组
* @param length
* @return
*/
private static int[] generateArray(int length) {
Random r = new Random();
int[] array = new int[length];
for (int i = 0; i array[j + 1]) {
int temp = array[j + 1];
array[j+1] = array[j];
array[j] = temp;
hasSwitch = true;//有数据交换
}
}
//没有数据交换退出循环
if (!hasSwitch) {
break;
}
}
}
/**
* 插入排序
*/
private static void insertionSort(int[] array) {
for (int i = 1; i = 0; j--) {
if (array[j] > value) {
array[j+1] = array[j];
} else {
break;
}
}
array[j+1] = value;
}
}
/**
* 打印数组
* @param array
*/
private static void print(int[] array) {
for(int i : array) {
System.out.print(i);
}
System.out.println();
}
}
打印结果:
随着数组长度的提升,冒泡排序比插入排序多出的耗时也随之增多。
开发中经常会遇到构造方法的参数很多,需要确认参数个数和位置;容易出现参数传错位的问题,而且 bug 不好排查。
如果使用默认构造方法,提供 public set 方法,又会把构造对象属性的修改权限放开,导致对象的属性数据安全问题。
这时候,可以使用 Builder 者模式。
package constxiong.interview.design;
/**
* 对象人
* @author ConstXiong
*/
public class Person {
/**
* id
*/
private final int id;
/**
* 姓名
*/
private final String name;
/**
* 性别
*/
private final String sex;
/**
* 身高
*/
private final Double height;
/**
* 体重
*/
private final Double weight;
public static class Builder {
private int id;
private String name;
private String sex;
private Double height;
private Double weight;
public Builder() {
}
public Builder id(int id) {
this.id = id;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder sex(String sex) {
this.sex = sex;
return this;
}
public Builder height(Double height) {
this.height = height;
return this;
}
public Builder weight(Double weight) {
this.weight = weight;
return this;
}
public Person build() {
return new Person(this);
}
}
private Person(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.sex = builder.sex;
this.height = builder.height;
this.weight = builder.weight;
}
}
创建 Person 对象的代码
Person person = new Person.Builder()
.id(1)
.name("ConstXiong")
.sex("男")
.height(1.70)
.weight(150.0)
.build();
Builder 模式需要注意是,Builder 类是静态内部类、类的构造方法是 private 的且参数为 Builder 对象。
Builder 模式不仅可以解决构造过程数据安全、参数过多、可读性的问题,还可以自动填充参数、为生成对象前对参数之间的关系进行合法校验等…
Builder 模式也带了新的问题:
order by 只有满足如下情况才会使用索引:
所以排序的性能并不高,尽量避免 order by
注意:
Array 即数组,声明方式可以如下:
int[] array = new int[3];
int array [] = new int[3];
int[] array = {1, 2, 3};
int[] array = new int[]{1, 2, 3};
定义一个 Array 时,必须指定数组的数据类型及数组长度,即数组中存放的元素个数固定并且类型相同。
ArrayList 是动态数组,长度动态可变,会自动扩容。不使用泛型的时候,可以添加不同类型元素。
List list = new ArrayList(3);
list.add(1);
list.add("1");
list.add(new Double("1.1"));
list.add("第四个元素,已经超过初始长度");
for (Object o : list) {
System.out.println(o);
}
使用 JDK中java.util.Collections 类,unmodifiable*** 方法赋值原集合。
当再修改集合时,会报错 java.lang.UnsupportedOperationException。从而确保自己定义的集合不被其他人修改。
public class TestCollectionUnmodify {
static List list = new ArrayList();
static Set set = new HashSet();
static Map map = new HashMap();
static {
list.add("1");
list.add("2");
list.add("3");
set.add("1");
set.add("2");
set.add("3");
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
}
public static void main(String[] args) {
list = Collections.unmodifiableList(list);
set = Collections.unmodifiableSet(set);
map = Collections.unmodifiableMap(map);
listModify();
setModify();
mapModify();
}
public static void listModify() {
list.add("4");
}
public static void setModify() {
set.add("4");
}
public static void mapModify() {
map.put("3", "4");
}
}
PS:guava工具类也可完成改功能
ReadWriteLock,读写锁。
ReentrantReadWriteLock 是 ReadWriteLock 的一种实现。
特点:
示例1–根据 key 获取 value 值
private ReadWriteLock lock = new ReentrantReadWriteLock();//定义读写锁
//根据 key 获取 value 值
public Object getValue(String key){
//使用读写锁的基本结构
lock.readLock().lock();//加读锁
Object value = null;
try{
value = cache.get(key);
if(value == null){
lock.readLock().unlock();//value值为空,释放读锁
lock.writeLock().lock();//加写锁,写入value值
try{
//重新检查 value值是否已经被其他线程写入
if(value == null){
value = "value";//写入数据
}
}finally{
lock.writeLock().unlock();
}
lock.readLock().lock();
}
}finally{
lock.readLock().unlock();
}
return value;
}
示例2–多线程环境下的读写锁使用
package constxiong.interview;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 测试可重入 读写锁
* @author ConstXiong
* @date 2019-06-10 11:19:42
*/
public class TestReentrantReadWriteLock {
private Map map = new HashMap();
private ReadWriteLock lock = new ReentrantReadWriteLock();
/**
* 根据 key 获取 value
* @param key
* @return
*/
public Object get(String key) {
Object value = null;
lock.readLock().lock();
try {
Thread.sleep(50L);
value = map.get(key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
return value;
}
/**
* 设置key-value
* @param key
* @return
*/
public void set(String key, Object value) {
lock.writeLock().lock();
try {
Thread.sleep(50L);
map.put(key, value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
//测试5个线程读数据,5个线程写数据
public static void main(String[] args) {
final TestReentrantReadWriteLock test = new TestReentrantReadWriteLock();
final String key = "lock";
final Random r = new Random();
for (int i = 0; i <5; i++) {
new Thread(){
@Override
public void run() {
for (int j = 0; j <10; j++) {
System.out.println(Thread.currentThread().getName() + " read value=" + test.get(key));
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int j = 0; j <10; j++) {
int value = r.nextInt(1000);
test.set(key, value);
System.out.println(Thread.currentThread().getName() + " write value=" + value);
}
}
}.start();
}
}
}
移动文件、目录;修改文件名或目录名
mv test.log test1.txt 将文件 test.log 重命名为 test1.txt
mv llog1.txt log2.txt log3.txt /test3 将文件 log1.txt,log2.txt,log3.txt 移动到 /test3 目录中
mv -i log1.txt log2.txt 将文件 log1.txt 改名为 log2.txt,如果 log2.txt 已经存在,则询问是否覆盖
mv * ../ 移动当前文件夹下的所有文件到上一级目录
Executors 类是从 JDK 1.5 开始就新增的线程池创建的静态工厂类,它就是创建线程池的,但是很多的大厂已经不建议使用该类去创建线程池。原因在于,该类创建的很多线程池的内部使用了无界任务队列,在并发量很大的情况下会导致 JVM 抛出 OutOfMemoryError,直接让 JVM 崩溃,影响严重。
1. newFixedThreadPool
创建定长线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程数量不再变化,当线程发生错误结束时,线程池会补充一个新的线程。
package constxiong.concurrency.a011;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 测试创建定长线程池
* @author ConstXiong
*/
public class TestNewFixedThreadPool {
public static void main(String[] args) {
//创建工作线程数为 3 的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程数量不再变化,当线程发生错误结束时,线程池会补充一个新的线程
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//提交 6 个任务
for (int i = 0; i <6; i++) {
final int index = i;
fixedThreadPool.execute(() -> {
try {
//休眠 3 秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " index:" + index);
});
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
//关闭线程池后,已提交的任务仍然会执行完
fixedThreadPool.shutdown();
}
}
打印结果:
pool-1-thread-2 index:1
pool-1-thread-3 index:2
pool-1-thread-1 index:0
4秒后...
pool-1-thread-1 index:4
pool-1-thread-3 index:5
pool-1-thread-2 index:3
2. newCachedThreadPool
创建可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制。
package constxiong.concurrency.a011;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 测试创建可缓存的线程池
* @author ConstXiong
*/
public class TestNewCachedThreadPool {
public static void main(String[] args) {
//创建可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i <6; i++) {
final int index = i;
cachedThreadPool.execute(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " index:" + index);
});
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
cachedThreadPool.shutdown();
}
}
打印结果可以看出,创建的线程数与任务数相等
pool-1-thread-1 index:0
pool-1-thread-3 index:2
pool-1-thread-6 index:5
pool-1-thread-4 index:3
pool-1-thread-5 index:4
pool-1-thread-2 index:1
4秒后...
3. newScheduledThreadPool
创建定长线程池,可执行周期性的任务。
package constxiong.concurrency.a011;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 测试创建定长线程池,可执行周期性的任务
* @author ConstXiong
*/
public class TestNewScheduledThreadPool {
public static void main(String[] args) {
//创建定长线程池,可执行周期性的任务
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
for (int i = 0; i <3; i++) {
final int index = i;
//scheduleWithFixedDelay 固定的延迟时间执行任务; scheduleAtFixedRate 固定的频率执行任务
scheduledThreadPool.scheduleWithFixedDelay(() -> {
System.out.println(Thread.currentThread().getName() + " index:" + index);
}, 0, 3, TimeUnit.SECONDS);
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
scheduledThreadPool.shutdown();
}
}
打印结果:
pool-1-thread-1 index:0
pool-1-thread-3 index:2
pool-1-thread-2 index:1
pool-1-thread-1 index:0
pool-1-thread-2 index:1
pool-1-thread-3 index:2
4秒后...
4. newSingleThreadExecutor
创建单线程的线程池,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行。
package constxiong.concurrency.a011;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 测试单线程的线程池
* @author ConstXiong
*/
public class TestNewSingleThreadExecutor {
public static void main(String[] args) {
//单线程的线程池,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
//提交 3 个任务
for (int i = 0; i <3; i++) {
final int index = i;
singleThreadPool.execute(() -> {
//执行第二个任务时,报错,测试线程池会创建新的线程执行任务三
if (index == 1) {
throw new RuntimeException("线程执行出现异常");
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " index:" + index);
});
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
singleThreadPool.shutdown();
}
}
打印结果可以看出,即使任务出现了异常,线程池还是会自动补充一个线程继续执行下面的任务
pool-1-thread-1 index:0
Exception in thread "pool-1-thread-1"
java.lang.RuntimeException: 线程执行出现异常
at constxiong.concurrency.a011.TestNewSingleThreadExecutor.lambda$0(TestNewSingleThreadExecutor.java:21)
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)
4秒后...
pool-1-thread-2 index:2
5. newSingleThreadScheduledExecutor
创建单线程可执行周期性任务的线程池。
package constxiong.concurrency.a011;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 测试单线程可执行周期性任务的线程池
* @author ConstXiong
*/
public class TestNewSingleThreadScheduledExecutor {
public static void main(String[] args) {
//创建单线程可执行周期性任务的线程池
ScheduledExecutorService singleScheduledThreadPool = Executors.newSingleThreadScheduledExecutor();
//提交 3 个固定频率执行的任务
for (int i = 0; i <3; i++) {
final int index = i;
//scheduleWithFixedDelay 固定的延迟时间执行任务; scheduleAtFixedRate 固定的频率执行任务
singleScheduledThreadPool.scheduleAtFixedRate(() -> {
System.out.println(Thread.currentThread().getName() + " index:" + index);
}, 0, 3, TimeUnit.SECONDS);
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
singleScheduledThreadPool.shutdown();
}
}
打印机结果可以看出 0-2 任务都被执行了 2 个周期
pool-1-thread-1 index:0
pool-1-thread-1 index:1
pool-1-thread-1 index:2
pool-1-thread-1 index:0
pool-1-thread-1 index:1
pool-1-thread-1 index:2
4秒后...
6. newWorkStealingPool
创建任务可窃取线程池,空闲线程可以窃取其他任务队列的任务,不保证执行顺序,适合任务耗时差异较大。
package constxiong.concurrency.a011;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 测试可任务窃取线程池
* @author ConstXiong
*/
public class TestNewWorkStealingPool {
public static void main(String[] args) {
//创建 4个工作线程的 任务可窃取线程池,如果不设置并行数,默认取 CPU 总核数
ExecutorService workStealingThreadPool = Executors.newWorkStealingPool(4);
for (int i = 0; i <10; i++) {
final int index = i;
workStealingThreadPool.execute(() -> {
try {
//模拟任务执行时间为 任务编号为0 1 2 的执行时间需要 3秒;其余任务200 毫秒,导致任务时间差异较大
if (index <= 2) {
Thread.sleep(3000);
} else {
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " index:" + index);
});
}
try {
Thread.sleep(10000);//休眠 10 秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("10秒后...");
}
}
打印结果可以看出,线程 ForkJoinPool-1-worker-0 把3-9的任务都执行完
ForkJoinPool-1-worker-0 index:3
ForkJoinPool-1-worker-0 index:4
ForkJoinPool-1-worker-0 index:5
ForkJoinPool-1-worker-0 index:6
ForkJoinPool-1-worker-0 index:7
ForkJoinPool-1-worker-0 index:8
ForkJoinPool-1-worker-0 index:9
ForkJoinPool-1-worker-1 index:0
ForkJoinPool-1-worker-3 index:2
ForkJoinPool-1-worker-2 index:1
10秒后...
Step1 A -> B : 你好,B。
Step2 A <- B : 收到。你好,A。
这样的两次握手过程, A 向 B 打招呼得到了回应,即 A 向 B 发送数据,B 是可以收到的。
但是 B 向 A 打招呼,A 还没有回应,B 没有收到 A 的反馈,无法确保 A 可以收到 B 发送的数据。
Step3 A -> B : 收到,B。
这样 B 才能确定 A 也可以收到 B 发送给 A 的数据。
参考
支持延迟加载的配置:
在配置文件的****标签内设置参数
resultMap 中配置 或
配置与测试示例
//配置文件
//Mapper xml
//InfoMapper
public interface InfoMapper {
@Select("select * from info where user_id = #{userId}")
@Results(value = {@Result(column="user_id", property = "userId")})
Info selectInfoByUserId(int userId);
}
//测试代码
System.out.println("------ selectUserWithLazyInfo ------");
User user = userMapper.selectUserWithLazyInfo();
System.out.println(user);
System.out.println(user.getInfo());
//打印 User 对象里的 Info 为空,使用 getInfo 能够查询对应的值
------ selectUserWithLazyInfo ------
User{id=1, name='ConstXiong1', mc='null', info=null, articles=null}
Info{userId=1, name=大熊}
实现原理:
支持延迟加载是通过字节码增强实现的,MyBatis 3.3 及以上默认使用了 javassist,3.3 以前使用 cglib 实现。
我本地用的 MyBatis 3.5.5,使用了 javassist 增强,核心源码如下
//DefaultResultSetHandler getRowValue 获取每条的查询数据,resultMap 中如果包含懒加载 rowValue 在 createResultObject 方法通过 javassist 代理增强
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//对象数据,通过 javassist 代理增强
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
//根据从数据库查询到的 resultSet,根据 resultMap 通过反射设置 rowValue 的值
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List> constructorArgTypes = new ArrayList<>();
final List constructorArgs = new ArrayList<>();
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
//如果返回对象的属性中包含懒加载,使用 javassist 代理增强,当设置属性值时被代理到 JavassistProxyFactory 的 invoke 方法
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
//JavassistProxyFactory 的 invoke 方法
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
final String methodName = method.getName();
try {
synchronized (lazyLoader) {
if (WRITE_REPLACE_METHOD.equals(methodName)) {
...
} else {
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
lazyLoader.loadAll();
} else if (PropertyNamer.isSetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
} else if (PropertyNamer.isGetter(methodName)) {
//测试代码中 user.getInfo() 方法的调用,在此执行懒加载查询关联 SQL 设置 info 属性
final String property = PropertyNamer.methodToProperty(methodName);
if (lazyLoader.hasLoader(property)) {
lazyLoader.load(property);
}
}
}
}
}
return methodProxy.invoke(enhanced, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
完整 Demo:
https://javanav.com/val/973ded541e9244aa8b3169b9fb869d60.html
IoC,Inversion of Control(控制反转)。
是一种设计思想,在Java开发中,将你设计好的对象交给容器控制,而不是显示地用代码进行对象的创建。
把创建和查找依赖对象的控制权交给 IoC 容器,由 IoC 容器进行注入、组合对象。这样对象与对象之间是松耦合、便于测试、功能可复用(减少对象的创建和内存消耗),使得程序的整个体系结构可维护性、灵活性、扩展性变高。
使用 IoC 的好处:
DI(Dependency Injection)依赖注入,是 IoC 容器装配、注入对象的一种方式。
通过依赖注入机制,简单的配置即可注入需要的资源,完成自身的业务逻辑,不需要关心资源的出处和具体实现。
spring 提供了三种主要的方式来配置 IoC 容器中的 bean
Spring 中 IoC 容器的底层实现就是 BeanFactory,BeanFactory 可以通过配置文件(xml、properties)、注解的方式加载 bean;提供根据 bean 的名称或类型类型查找 bean 的能力。功能最全的一个 BeanFactory 实现就是 DefaultListableBeanFactory。
作用:都是给变量 i 加 1,相当于 i = i + 1;
区别:
i++ 先运算后加 1
++i 先加 1 再运算
package constxiong.interview;
/**
测试 ++i 和 i++
@author ConstXiong
@date 2019-10-17 13:44:05
*/
public class TestAdd {
public static void main(String[] args) {
int a = 3;
int b = a++;
System.out.println(“a=” + a);
System.out.println(“b=” + b);
int x = 3;
int y = ++x;
System.out.println("x=" + x);
System.out.println("y=" + y);
}
}
打印
a=4
b=3
x=4
y=4
生命周期:
//servlet 容器启动时会创建 Filter 实例
public void init(FilterConfig filterConfig) throws ServletException;
//在每次访问目标资源时执行
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
//服务器关闭时销毁Filter对象
public void destroy();
作用:
Java 7 开始,新引入的字节码指令,可以实现一些动态类型语言的功能。Java 8 的 Lambda 表达式就是通过 invokedynamic 指令实现,使用方法句柄实现。
元空间在本地内存上,默认是没有上限的,不加限制出了问题会影响整个服务器的,所以也是比较危险的。-XX:MaxMetaspaceSize 可以指定最大值。
一般使用动态代理的框架会生成很多 Java 类,如果占用空间超出了我们的设定最大值,会发生元空间溢出。
递归:直接或间接调用自身算法的过程
满足使用递归的条件:
优点:
缺点:
锁的级别从低到高:
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
锁分级别原因:
没有优化以前,synchronized 是重量级锁(悲观锁),使用 wait 和 notify、notifyAll 来切换线程状态非常消耗系统资源;线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能。所以 JVM 对 synchronized 关键字进行了优化,把锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。
锁升级的目的是为了减低锁带来的性能消耗,在 Java 6 之后优化 synchronized 为此方式。
**无锁:**没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。
**偏向锁:**对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带来的性能开销。偏向锁,指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。
偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁;
如果线程处于活动状态,升级为轻量级锁的状态。
**轻量级锁:**轻量级锁是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁,线程 B 会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。
当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。
**重量级锁:**指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。
锁状态对比:
synchronized 锁升级的过程:
部分内容摘自:
答案:D
分析:
创建子类对象,先执行父类的构造方法,再执行子类的构造方法
web工程中的web.xml文件:
注解:
@WebServlet(name="servlet", urlPatterns={"/*"})
InnoDB
MyISAM
其他表引擎:Archive、Blackhole、CSV、Memory
开放式系统互联通信参考模型(英语:Open System Interconnection Reference Model,缩写:OSI;简称为OSI模型)是一种概念模型,由国际标准化组织提出,一个试图使各种计算机在世界范围内互连为网络的标准框架。
OSI模型分为七层,自下而上为 物理层(Physical Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表达层(Presentation Layer)、应用层(Application Layer)。
参考:
以下情况可能导致写操作丢失:
不能,抽象类是被用于继承的,final修饰代表不可修改、不可继承的。
线程死锁是指由于两个或者多个线程互相持有所需要的资源,导致这些线程一直处于等待其他线程释放资源的状态,无法继续执行,如果线程都不主动释放所占有的资源,将产生死锁。
当线程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
产生原因:
…
如线程A占有资源 1 的锁,去竞争资源 2 的锁;线程 B 占有资源 2 的锁,去竞争资源1的锁。
代码表现如下
package constxiong.concurrency.a022;
/**
* 测试死锁
* @author ConstXiong
* @date 2019-09-23 19:28:23
*/
public class TestDeadLock {
final static Object o1 = new Object();
final static Object o2 = new Object();
public static void main(String[] args) {
//先持有 o1 的锁,再去获取 o2 的锁
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (o1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 o1 对象的锁");
try {
System.out.println("休眠1秒");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + " 去获取 o2 对象的锁");
synchronized (o2) {
System.out.println("线程:" + Thread.currentThread().getName() + " 成功获取 o2 对象的锁");
}
}
}
};
//先持有 o2 的锁,再去获取 o1 的锁
Thread t2 = new Thread() {
@Override
public void run() {
synchronized (o2) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 o2 对象的锁");
try {
System.out.println("休眠1秒");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + " 去获取 o1 对象的锁");
synchronized (o1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 成功获取 o1 对象的锁");
}
}
}
};
t1.start();
t2.start();
}
}
测试结果,发生死锁,打印如下
线程:Thread-0 获取到 o1 对象的锁
休眠1秒
线程:Thread-1 获取到 o2 对象的锁
休眠1秒
线程:Thread-1 去获取 o1 对象的锁
线程:Thread-0 去获取 o2 对象的锁
什么样的对象会被当做垃圾回收?
当一个对象的地址没有变量去引用时,该对象就会成为垃圾对象,垃圾回收器在空闲的时候会对其进行内存清理回收
如何检验对象是否被回收?
可以重写 Object 类中的 finalize 方法,这个方法在垃圾收集器执行的时候,被收集器自动调用执行的
怎样通知垃圾收集器回收对象?
可以调用 System 类的静态方法 gc(),通知垃圾收集器去清理垃圾,但不能保证收集动作立即执行,具体的执行时间取决于垃圾收集的算法
不一定。如
public abstract class TestAbstractClass {
public static void notAbstractMethod() {
System.out.println("I am not a abstract method.");
}
}
String 的 replaceAll 是基于正则表达式实现的,借助 JDK 中正则表达式实现。
package constxiong.interview;
import java.util.regex.Pattern;
/**
* 测试实现 replaceAll 方法
* @author ConstXiong
*/
public class TestReplaceAll {
public static void main(String[] args) {
String s = "01234abcd";
System.out.println(replaceAll(s, "[a-z]", "CX"));
}
public static String replaceAll(String s, String regex, String replacement) {
return Pattern.compile(regex).matcher(s).replaceAll(replacement);
}
}
第一段编译报错,s1 + 1自动升级为 int 型,int 型赋值给 s1,需要手动强转
第二段隐含类型强转,不会报错
在了解什么是 Java 内存模型之前,先了解一下为什么要提出 Java 内存模型。
之前提到过并发编程有三大问题
为了解决并发编程的三大问题,提出了 JSR-133,新的 Java 内存模型,JDK 5 开始使用。
那么什么是 Java 内存模型呢?
现在说的 Java 内存模型,一般是指 JSR-133: Java Memory Model and Thread Specification Revision 规定的 Java 内存模型。
JSR-133 具体描述:jsr133.pdf
JSR-133 在 JCP 官网的具体描述
说明下
JSR:Java Specification Requests,Java 规范提案。
JCP:Java Community Process 是一个开放的国际组织,成立于1998年,主要由 Java 开发者以及被授权者组成,是使有兴趣的各方参与定义 Java 的特征和未来版本的正式过程。
简单总结下
参考:
instanceof 运算符是用来在运行时判断对象是否是指定类及其父类的一个实例。
比较的是对象,不能比较基本类型
使用如下
package constxiong.interview;
/**
* 测试 instanceof
* @author ConstXiong
* @date 2019-10-23 11:05:21
*/
public class TestInstanceof {
public static void main(String[] args) {
A a = new A();
AA aa = new AA();
AAA aaa = new AAA();
System.out.println(a instanceof A);//true
System.out.println(a instanceof AA);//false
System.out.println(aa instanceof AAA);//false
System.out.println(aaa instanceof A);//true
}
}
class A {
}
class AA extends A {
}
class AAA extends AA {
}
ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方式,避免资源在多线程间共享。
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
ThreadLocal 使用例子:
public class TestThreadLocal {
//线程本地存储变量
private static final ThreadLocal THREAD_LOCAL_NUM = new ThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
for (int i = 0; i <3; i++) {//启动三个线程
Thread t = new Thread() {
@Override
public void run() {
add10ByThreadLocal();
}
};
t.start();
}
}
/**
* 线程本地存储变量加 5
*/
private static void add10ByThreadLocal() {
for (int i = 0; i <5; i++) {
Integer n = THREAD_LOCAL_NUM.get();
n += 1;
THREAD_LOCAL_NUM.set(n);
System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
}
}
}
打印结果:启动了 3 个线程,每个线程最后都打印到 “ThreadLocal num=5”,而不是 num 一直在累加直到值等于 15
Thread-0 : ThreadLocal num=1
Thread-1 : ThreadLocal num=1
Thread-0 : ThreadLocal num=2
Thread-0 : ThreadLocal num=3
Thread-1 : ThreadLocal num=2
Thread-2 : ThreadLocal num=1
Thread-0 : ThreadLocal num=4
Thread-2 : ThreadLocal num=2
Thread-1 : ThreadLocal num=3
Thread-1 : ThreadLocal num=4
Thread-2 : ThreadLocal num=3
Thread-0 : ThreadLocal num=5
Thread-2 : ThreadLocal num=4
Thread-2 : ThreadLocal num=5
Thread-1 : ThreadLocal num=5
多个指令之间没有依赖关系,可以使用 pipeline 一次性执行多个指令,减少 IO,缩减时间。
Java 语言中有四种权限访问控制符,能够控制类中成员变量和方法的可见性。
public
被 public 修饰的成员变量和方法可以在任何类中都能被访问到。
被 public 修饰的类,在一个 java 源文件中只能有一个类被声明为 public ,而且一旦有一个类为 public ,那这个 java 源文件的文件名就必须要和这个被 public 所修饰的类的类名相同,否则编译不能通过。
protected
被 protected 修饰的成员会被位于同一 package 中的所有类访问到,也能被该类的所有子类继承下来。
friendly
默认,缺省的。在成员的前面不写访问修饰符的时候,默认就是友好的。
同一package中的所有类都能访问。
被 friendly 所修饰的成员只能被该类所在同一个 package 中的子类所继承下来。
private
私有的。只能在当前类中被访问到。
答案是不一定。存在很多特殊情况导致 finally 语句块不执行。如:
等…
代码如下
public static String test() {
String str = null;
int i = 0;
if (i == 0) {
return str;//直接返回未执行到finally语句块
}
try {
System.out.println("try...");
return str;
} finally {
System.out.println("finally...");
}
}
public static String test2() {
String str = null;
int i = 0;
i = i / 0;//抛出异常未执行到finally语句块
try {
System.out.println("try...");
return str;
} finally {
System.out.println("finally...");
}
}
public static String test3() {
String str = null;
try {
System.out.println("try...");
System.exit(0);//系统退出未执行到finally语句块
return str;
} finally {
System.out.println("finally...");
}
}
显示当前系统正在执行的进程的 ID、内存占用率、CPU 占用率等相关信息
常用参数:
-c 显示完整的进程命令
-s 保密模式
-p <进程号> 指定进程显示
-n <次数>循环显示次数
实例:
top - 00:05:02 up 204 days, 9:56, 2 users, load average: 0.00, 0.01, 0.05
Tasks: 68 total, 1 running, 67 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.7 us, 0.7 sy, 0.0 ni, 98.3 id, 0.3 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1016168 total, 65948 free, 335736 used, 614484 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 517700 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7110 root 10 -10 130476 9416 6116 S 1.3 0.9 141:26.59 AliYunDun
15845 root 20 0 47064 4320 2180 S 0.3 0.4 2:51.16 nginx
前五行是当前系统情况整体的统计信息区
第一行,任务队列信息,同 uptime 命令的执行结果:
00:05:02 — 当前系统时间
up 204 days, 9:56 — 系统已经连续运行了 204 天 9 小时 56 分钟未重启
2 users — 当前有 2 个用户登录系统
load average: 0.00, 0.01, 0.05 — load average 后面的三个数分别是 0 分钟、1 分钟、5分钟的负载情况,load average 数据是每隔 5 秒钟检查一次活跃的进程数,然后按特定算法计算出的数值。如果这个数除以逻辑CPU的数量,结果高于5的时候就表明系统在超负荷运转了
第二行,Tasks — 任务(进程):
系统现在共有 68 个进程,其中处于运行中的有 1 个,休眠中 67 个,停止 0 个,僵死 0个
第三行,cpu状态信息:
0.7 us — 用户空间占用 CPU 的百分比
0.7 sy — 内核空间占用 CPU 的百分比
0.0 ni — 改变过优先级的进程占用 CPU 的百分比
98.3 id — 空闲CPU百分比
0.3 wa — IO 等待占用 CPU 的百分比
0.0 hi — 硬中断(Hardware IRQ)占用 CPU 的百分比
0.0 si — 软中断(Software Interrupts)占用 CPU 的百分比
0.0 st - 虚拟机占用百分比
第四行,内存状态:
1016168 total — 物理内存总量
65948 free — 空闲内存总量
335736 used — 使用中的内存总量
614484 buff/cache — 缓存的内存量
第五行,swap交换分区信息,具体信息说明如下:
0 total — 交换区总量
0 free — 空闲交换区总量
0 used — 使用的交换区总量
517700 avail Mem - 可用内存
第七行以下:各进程(任务)的状态监控,项目列信息说明如下:
PID — 进程id
USER — 进程所有者
PR — 进程优先级
NI — nice值。负值表示高优先级,正值表示低优先级
VIRT — 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
RES — 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
SHR — 共享内存大小,单位kb
S — 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
%CPU — 上次更新到现在的CPU时间占用百分比
%MEM — 进程使用的物理内存百分比
TIME+ — 进程使用的CPU时间总计,单位1/100秒
COMMAND — 进程名称(命令名/命令行)
top 交互命令
h 显示top交互命令帮助信息
c 切换显示命令名称和完整命令行
m 以内存使用率排序
P 根据CPU使用百分比大小进行排序
T 根据时间/累计时间进行排序
W 将当前设置写入~/.toprc文件中
o或者O 改变显示项目的顺序
重点说下 FactoryBean,该接口包含 3 个方法 getObject、getObjectType、isSingleton,用于构建复杂的 bean。
如,MyBatis 与 Spring 集成,使用了 SqlSessionFactoryBean 继承 FactoryBean,用于构建 SqlSessionFactory bean。
得到 FactoryBean 本身这个 bean,需要在 bean name 前面加上 $
Oracle 数据库有 4 种触发器
DML:当发出UPDATE、INSERT、DELETE命令就可以触发已定义好的 DML 触发器
语法:
create or replace trigger trigger_name
after|before insert|update|delete
on table_name
for each row
Instead-of:向一个由多个表联接成的视图作 DML 操作时可以用 Instead-of 触发器
语法:
create or replace trigger trigger_name
instead of insert|update|delete
on view_name
for each row
DDL:当发出CREATE、ALTER、DROP、TRUNCATE命令时会触发已定义好的DDL触发器,这种触发器可以用来监控某个用户或整个数据库的所有对象的结构变化
语法:
create or replace trigger trigger_name
before|after create|alter|drop|truncate
on schema|database
DB:当STARTUP、SHUTDOWN、LOGON、LOGOFF数据库时就会触发DB事件触发器,这种触发器可以用来监控数据库什么时候关闭/打,或者用户的LOGON/LOGOFF数据库情况
语法:
create or replace trigger trigger_name
before|after startup|shutdown|logon|logoff
on database
一般应用系统中用到 DML、Instead-of;DDL、DB 两种触发器是 DBA 管理数据库用得比较多
要创建 DDL 和 DB 这两种触发器必须有 DBA 的权限
都不能
MySQL 最常用的集群部署方式是主从架构,可以 1 主多从,主库写,从库读,用这种方式来做读写分离。也可以主主架构,两边都可以读写,但需要业务代码控制数据冲突问题。MGR(MySQL Group Replication),是分布式架构,支持多点写入,但性能不如上述两者,且对网络要求较高。
常用的读写分离基于主从架构实现的较多。
以 64 位 windows MySQL 最新版的 server,8.0.21 安装为例。
step1、安装流程:
第二台电脑也是如此配置。这样两台电脑的 MySQL 服务安装就搞定了。
step2、配置主从
配置到这里,向主库新建表,增删改数据,都会自动同步到从库。
具体配置说明:
二进制文件程序名搜索
whereis 及 locate 都是基于系统内建的数据库进行搜索,效率很高,而 find 则是遍历硬盘查找文件
常用参数:
-b 定位可执行文件
-m 定位帮助文件
-s 定位源代码文件
-u 搜索默认路径下除可执行文件、源代码文件、帮助文件以外的其它文件
whereis locale 查找 locale 程序相关文件
whereis -s locale 查找 locale 的源码文件
whereis -m locale 查找 locale 的帮助文件
-128 至 127
删除目录,不能删除非空目录,当子目录被删除后父目录也成为空目录的话,则一并删除
rmdir -p a/b/c 等同于 rmdir a/b/c a/b a
复制一个 Java 对象
浅拷贝:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针,不复制堆内存中的对象。
深拷贝:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针和堆内存中的对象。
![20190618154423857_20190922012336.png][]
[20190618154423857_20190922012336.png]: https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fwww.javanav.com%2F%2Faimgs%2F20190618154423857__20190922012336.png&pos_id=img-p43m8OFd-1702467223529)
下面将主查询的表称为外表;子查询的表称为内表。exists 与 in 的主要区别如下:
详细参考:
Mysql 中 exists 和 in 的区别
以顺序 b,a,time 建立联合索引,CREATE INDEX idx_b_a_time ON table(b,a,time)。
新 MySQL 版本会优化 WHERE 子句后面的列顺序,以匹配联合索引顺序
实现多态的三个条件
向上转型:将一个父类的引用指向一个子类对象,自动进行类型转换。
向下转型:将一个指向子类对象的引用赋给一个子类的引用,必须进行强制类型转换。
默认 Failover Cluster
显示磁盘空间使用情况
获取硬盘被占用空间,剩余空间等信息。默认所有当前被挂载的文件系统的可用空间都会显示
默认情况下,磁盘空间以 1KB 为单位进行显示
常用参数:
-a 全部文件系统列表
-h 以方便阅读的方式显示信息
-i 显示inode信息
-k 区块为1024字节
-l 只显示本地磁盘
-T 列出文件系统类型
流程与 Mapper 接口与 xml 绑定类似。
分析
解析生成注册 MapperStatement 的代码入口在 MapperRegistry addMapper 方法
//使用 MapperProxyFactory 包装 Mapper 接口 Class 对象
knownMappers.put(type, new MapperProxyFactory<>(type));
//解析 Mapper 接口方法上的注解,生成对应的 MapperStatement
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
获取 Mapper 接口的动态代理对象的代码入口在 MapperRegistry getMapper 方法
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
if-else-if-else:
switch:
2 <<3
补充:当这个数接近Java基本整数类型的最大值时,左移位运算可能出现溢出,得出负值。
1、Java Virtual Machine(Java虚拟机)的缩写
2、实现跨平台的最核心的部分
3、.class 文件会在 JVM 上执行,JVM 会解释给操作系统执行
4、有自己的指令集,解释自己的指令集到 CPU 指令集和系统资源的调用
5、JVM 只关注被编译的 .class 文件,不关心 .java 源文件
构造方法可以被重载
构造方法不可以被重写
Zookeeper 注册中心: 基于分布式协调系统 Zookeeper 实现,采用 Zookeeper 的 watch 机制实现数据变更(官方推荐)
Multicast 注册中心: 基于网络中组播传输实现,不需要任何中心节点,只要广播地址,就能进行服务注册和发现
Redis 注册中心: 基于 Redis 实现,采用 key/Map 数据结构存储,主 key 存储服务名和类型,Map 中 key 存储服务 URL,Map 中 value 存储服务过期时间,基于 Redis 的发布/订阅模式通知数据变更
Simple 注册中心:一个普通的 Dubbo 服务,可以减少第三方依赖,使整体通讯方式一致,不支持集群
Java 中断言有两种语法形式:
表达式1 是一个布尔值
错误表达式可以得出一个值,用于生成显示调试信息的字符串消息
package constxiong.interview;
public class TestAssert {
public static void main(String[] args) {
assert 1 > 0;
int x = 1;
assert x <0 : "大于0";
}
}
打印:
Exception in thread "main" java.lang.AssertionError: 大于0
at constxiong.interview.TestAssert.main(TestAssert.java:8)
1、值不同,使用 == 和 equals() 比较都返回 false
2、值相同
使用 == 比较:
基本类型 - 基本类型、基本类型 - 包装对象返回 true
包装对象 - 包装对象,非同一个对象(对象的内存地址不同)返回 false;对象的内存地址相同返回 true,如下值等于 100 的两个 Integer 对象(原因是 JVM 缓存部分基本类型常用的包装类对象,如 Integer -128 ~ 127 是被缓存的)
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1i2); //打印true
System.out.println(i3i4); //打印false
使用 equals() 比较
3、不同类型的对象对比,返回 false
JDK1.8,实验代码
byte b1 = 127;
Byte b2 = new Byte("127");
Byte b3 = new Byte("127");
System.out.println("Byte 基本类型和包装对象使用 == 比较 : " + (b1 == b2));
System.out.println("Byte 基本类型和包装对象使用 equals 比较 : " + b2.equals(b1));
System.out.println("Byte 包装对象和包装对象使用 == 比较 : " + (b2 == b3));
System.out.println("Byte 包装对象和包装对象使用 equals 比较 : " + b2.equals(b3));
System.out.println();
short s1 = 12;
Short s2 = new Short("12");
Short s3 = new Short("12");
System.out.println("Short 基本类型和包装对象使用 == 比较 : " + (s1 == s2));
System.out.println("Short 基本类型和包装对象使用 equals 比较 : " + s2.equals(s1));
System.out.println("Short 包装对象和包装对象使用 == 比较 : " + (s2 == s3));
System.out.println("Short 包装对象和包装对象使用 equals 比较 : " + s2.equals(s3));
System.out.println();
char c1 = 'A';
Character c2 = new Character('A');
Character c3 = new Character('A');
System.out.println("Character 基本类型和包装对象使用 == 比较 : " + (c1 == c2));
System.out.println("Character 基本类型和包装对象使用 equals 比较 : " + c2.equals(c1));
System.out.println("Character 包装对象和包装对象使用 == 比较 : " + (c2 == c3));
System.out.println("Character 包装对象和包装对象使用 equals 比较 : " + c2.equals(c3));
System.out.println();
int i1 = 10000;
Integer i2 = new Integer(10000);
Integer i3 = new Integer(10000);
System.out.println("Integer 基本类型和包装对象使用 == 比较 : " + (i1 == i2));
System.out.println("Integer 基本类型和包装对象使用 equals 比较 : " + i2.equals(i1));
System.out.println("Integer 包装对象和包装对象使用 == 比较 : " + (i2 == i3));
System.out.println("Integer 包装对象和包装对象使用 equals 比较 : " + i2.equals(i3));
System.out.println();
long l1 = 1000000000000000L;
Long l2 = new Long("1000000000000000");
Long l3 = new Long("1000000000000000");
System.out.println("Long 基本类型和包装对象使用 == 比较 : " + (l1 == l2));
System.out.println("Long 基本类型和包装对象使用 equals 比较 : " + l2.equals(l1));
System.out.println("Long 包装对象和包装对象使用 == 比较 : " + (l2 == l3));
System.out.println("Long 包装对象和包装对象使用 equals 比较 : " + l2.equals(l3));
System.out.println();
float f1 = 10000.111F;
Float f2 = new Float("10000.111");
Float f3 = new Float("10000.111");
System.out.println("Float 基本类型和包装对象使用 == 比较 : " + (f1 == f2));
System.out.println("Float 基本类型和包装对象使用 equals 比较 : " + f2.equals(f1));
System.out.println("Float 包装对象和包装对象使用 == 比较 : " + (f2 == f3));
System.out.println("Float 包装对象和包装对象使用 equals 比较 : " + f2.equals(f3));
System.out.println();
double d1 = 10000.111;
Double d2 = new Double("10000.111");
Double d3 = new Double("10000.111");
System.out.println("Double 基本类型和包装对象使用 == 比较 : " + (d1 == d2));
System.out.println("Double 基本类型和包装对象使用 equals 比较 : " + d2.equals(d1));
System.out.println("Double 包装对象和包装对象使用 == 比较 : " + (d2 == d3));
System.out.println("Double 包装对象和包装对象使用 equals 比较 : " + d2.equals(d3));
System.out.println();
boolean bl1 = true;
Boolean bl2 = new Boolean("true");
Boolean bl3 = new Boolean("true");
System.out.println("Boolean 基本类型和包装对象使用 == 比较 : " + (bl1 == bl2));
System.out.println("Boolean 基本类型和包装对象使用 equals 比较 : " + bl2.equals(bl1));
System.out.println("Boolean 包装对象和包装对象使用 == 比较 : " + (bl2 == bl3));
System.out.println("Boolean 包装对象和包装对象使用 equals 比较 : " + bl2.equals(bl3));
运行结果
Byte 基本类型和包装对象使用 == 比较 : true
Byte 基本类型和包装对象使用 equals 比较 : true
Byte 包装对象和包装对象使用 == 比较 : false
Byte 包装对象和包装对象使用 equals 比较 : true
Short 基本类型和包装对象使用 == 比较 : true
Short 基本类型和包装对象使用 equals 比较 : true
Short 包装对象和包装对象使用 == 比较 : false
Short 包装对象和包装对象使用 equals 比较 : true
Character 基本类型和包装对象使用 == 比较 : true
Character 基本类型和包装对象使用 equals 比较 : true
Character 包装对象和包装对象使用 == 比较 : false
Character 包装对象和包装对象使用 equals 比较 : true
Integer 基本类型和包装对象使用 == 比较 : true
Integer 基本类型和包装对象使用 equals 比较 : true
Integer 包装对象和包装对象使用 == 比较 : false
Integer 包装对象和包装对象使用 equals 比较 : true
Long 基本类型和包装对象使用 == 比较 : true
Long 基本类型和包装对象使用 equals 比较 : true
Long 包装对象和包装对象使用 == 比较 : false
Long 包装对象和包装对象使用 equals 比较 : true
Float 基本类型和包装对象使用 == 比较 : true
Float 基本类型和包装对象使用 equals 比较 : true
Float 包装对象和包装对象使用 == 比较 : false
Float 包装对象和包装对象使用 equals 比较 : true
Double 基本类型和包装对象使用 == 比较 : true
Double 基本类型和包装对象使用 equals 比较 : true
Double 包装对象和包装对象使用 == 比较 : false
Double 包装对象和包装对象使用 equals 比较 : true
Boolean 基本类型和包装对象使用 == 比较 : true
Boolean 基本类型和包装对象使用 equals 比较 : true
Boolean 包装对象和包装对象使用 == 比较 : false
Boolean 包装对象和包装对象使用 equals 比较 : true
ps:可以延伸一个问题,基本类型与包装对象的拆/装箱的过程
单列索引是基于单列所创建的索引,复合索引是基于两列或者多列所创建的索引
唯一索引是索引列值不能重复的索引,非唯一索引是索引列可以重复的索引。都允许取 NULL 值,默认 Oracle 创建的索引是不唯一索引
B 树索引是按 B 树算法组织并存放索引数据的,B 树索引主要依赖其组织并存放索引数据的算法来实现快速检索功能
它采用位图偏移方式来与表的行 ROWID 号对应,通过位图索引中的映射函数完成位到行的 ROWID 的转换
主要用于节省空间,减少oracle对数据块的访问
采用位图索引一般是重复值太多、只有几个枚举值的表字段
Oracle 对包含列的函数或表达式创建的索引
工厂模式:Spring 使用工厂模式,通过 BeanFactory 来创建对象
单例模式:Bean 默认就是单例模式
策略模式:Resource 的实现类,针对不同的资源文件,实现了不同方式的资源获取策略
代理模式:Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码提升
模板方法模式:父类生成代码骨架,具体实现延迟到子类,如 JdbcTemplate、RestTemplate
适配器模式:Spring AOP 中的 Advice 使用到了适配器模式,Spring MVC 中用到了适配器模式适配 Controller
观察者模式:Spring 事件驱动模型就是观察者模式
…
Java 中 volatile 关键字是一个类型修饰符。JDK 1.5 之后,对其语义进行了增强。
看一下我们之前的一个可见性问题的测试例子
package constxiong.concurrency.a014;
/**
* 测试可见性问题
* @author ConstXiong
*/
public class TestVisibility {
//是否停止 变量
private static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
//启动线程 1,当 stop 为 true,结束循环
new Thread(() -> {
System.out.println("线程 1 正在运行...");
while (!stop) ;
System.out.println("线程 1 终止");
}).start();
//休眠 10 毫秒
Thread.sleep(10);
//启动线程 2, 设置 stop = true
new Thread(() -> {
System.out.println("线程 2 正在运行...");
stop = true;
System.out.println("设置 stop 变量为 true.");
}).start();
}
}
程序会一直循环运行下去
这个就是因为 CPU 缓存导致的可见性导致的问题。
线程 2 设置 stop 变量为 true,线程 1 在 CPU 1上执行,读取的 CPU 1 缓存中的 stop 变量仍然为 false,线程 1 一直在循环执行。
示意如图:
给 stop 变量加上 valatile 关键字修饰就可以解决这个问题。
对一个 volatile 变量的写 happens-before 任意后续对这个 volatile 变量的读
在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作
happens-before 传递性,A happens-before B,B happens-before C,则 A happens-before C
在程序运行时,为了提高执行性能,在不改变正确语义的前提下,编译器和 CPU 会对指令序列进行重排序。
Java 编译器会在生成指令时,为了保证在不同的编译器和 CPU 上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的指令重排序
编译器根据具体的底层体系架构,将这些内存屏障替换成具体的 CPU 指令
内存屏障会告诉编译器和 CPU:不管什么指令都不能和这条 Memory Barrier 指令重排序
我觉得,有序性最经典的例子就是 JDK 并发包中的显式锁 java.util.concurrent.locks.Lock 的实现类对有序性的保障。
以下摘自:http://ifeve.com/java锁是如何保证数据可见性的/
实现 Lock 的代码思路简化为
private volatile int state;
void lock() {
read state
if (can get lock)
write state
}
void unlock() {
write state
}
Happens-before 规则:一个 volatile 变量的写操作发生在这个 volatile 变量随后的读操作之前。
搜索文档数据库命令
locate 通过搜寻系统内建文档数据库达到快速找到档案,数据库由 updatedb 程序来更新,updatedb 由 cron daemon 周期性调用
locate 命令在搜寻较快,但最近才建立或刚更名的,可能会找不到
locate 与 find 命令相似,可以使用正则匹配查找
常用参数:
-l num 要显示的行数
-f 将特定的档案系统排除在外
-r 使用正则运算式做为寻找条件
locate pwd 查找文件名中包含 pwd 的所有文件
locate /etc/sh 搜索 etc 目录下所有以 sh 开头的文件
locate -r '^/var.*txt$' 查找 /var 目录下,以 txt 结尾的文件
区别:doPost 用来处理 post 请求,doGet 用来处理 get 请求
参数:传递的参数相同的都是 HttpServletRequest 和 HttpServletResponse
修饰普通方法,执行方法代码,需要获取对象本身 this 的锁
package constxiong.concurrency.a18;
import java.util.ArrayList;
import java.util.List;
/**
测试 synchronized 普通方法
@author ConstXiong
@date 2019-09-19 10:49:46
*/
public class TestSynchronizedNormalMethod {
private int count = 0;
// private void add1000() {
private synchronized void add1000() { //使用 synchronized 修饰 add100 方法,即可获得正确的值 30000
for (int i = 0; i <1000; i++) {
count++;
}
}
//启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性加 1000
private void test() throws InterruptedException {
List threads = new ArrayList(10);
for (int i = 0; i <30; i++) {
Thread t = new Thread(() -> {
add1000();
});
t.start();
threads.add(t);
}
//等待所有线程执行完毕
for (Thread t : threads) {
t.join();
}
//打印 count 的值
System.out.println(count);
}
public static void main(String[] args) throws InterruptedException {
//创建 TestSynchronizedNormalMethod 对象,调用 test 方法
new TestSynchronizedNormalMethod().test();
}
}
修饰静态方法,执行方法代码,需要获取 class 对象的锁
package constxiong.concurrency.a18;
import java.util.ArrayList;
import java.util.List;
/**
测试 synchronized 静态方法
@author ConstXiong
@date 2019-09-19 10:49:46
*/
public class TestSynchronizedStaticMethod {
private static int count = 0;
private static void add1000() {
// private synchronized static void add1000() { //使用 synchronized 修饰 add100 方法,即可获得正确的值 30000
for (int i = 0; i <1000; i++) {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
//启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性加 1000
List threads = new ArrayList(10);
for (int i = 0; i <30; i++) {
Thread t = new Thread(() -> {
add1000();
});
t.start();
threads.add(t);
}
//等待所有线程执行完毕
for (Thread t : threads) {
t.join();
}
//打印 count 的值
System.out.println(count);
}
}
锁定 Java 对象,修饰代码块,显示指定需要获取的 Java 对象锁
package constxiong.concurrency.a18;
import java.util.ArrayList;
import java.util.List;
/**
测试 synchronized 代码块
@author ConstXiong
@date 2019-09-19 10:49:46
*/
public class TestSynchronizedCodeBlock {
private int count = 0;
//锁定的对象
private final Object obj = new Object();
private void add1000() {
//执行下面的加 1000 的操作,都需要获取 obj 这个对象的锁
synchronized (obj) {
for (int i = 0; i <1000; i++) {
count++;
}
}
}
//启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性加 1000
private void test() throws InterruptedException {
List threads = new ArrayList(10);
for (int i = 0; i <30; i++) {
Thread t = new Thread(() -> {
add1000();
});
t.start();
threads.add(t);
}
//等待所有线程执行完毕
for (Thread t : threads) {
t.join();
}
//打印 count 的值
System.out.println(count);
}
public static void main(String[] args) throws InterruptedException {
//创建 TestSynchronizedNormalMethod 对象,调用 test 方法
new TestSynchronizedCodeBlock().test();
}
}
package constxiong.concurrency.a18;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试 ReentrantLock
* @author ConstXiong
* @date 2019-09-19 11:26:50
*/
public class TestReentrantLock {
private int count = 0;
private final Lock lock = new ReentrantLock();
private void add1000() {
lock.lock();
try {
for (int i = 0; i <1000; i++) {
count++;
}
} finally {
lock.unlock();
}
}
//启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性加 1000
private void test() throws InterruptedException {
List threads = new ArrayList(10);
for (int i = 0; i <30; i++) {
Thread t = new Thread(() -> {
add1000();
});
t.start();
threads.add(t);
}
//等待所有线程执行完毕
for (Thread t : threads) {
t.join();
}
//打印 count 的值
System.out.println(count);
}
public static void main(String[] args) throws InterruptedException {
//创建 TestReentrantLock 对象,调用 test 方法
new TestReentrantLock().test();
}
}
package constxiong.concurrency.a18;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 测试可重入读写锁 ReentrantReadWriteLock
* @author ConstXiong
* @date 2019-09-19 11:36:19
*/
public class TestReentrantReadWriteLock {
//存储 key value 的 map
private Map map = new HashMap();
//读写锁
private final ReadWriteLock lock = new ReentrantReadWriteLock();
/**
* 根据 key 获取 value
* @param key
*/
public Object get(String key) {
Object value = null;
lock.readLock().lock();
try {
Thread.sleep(50L);
value = map.get(key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
return value;
}
/**
* 设置key-value
* @param key
*/
public void set(String key, Object value) {
lock.writeLock().lock();
try {
Thread.sleep(50L);
map.put(key, value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
//测试5个线程读数据,5个线程写数据
public static void main(String[] args) {
//创建测试可重入读写锁 TestReentrantReadWriteLock 对象
TestReentrantReadWriteLock test = new TestReentrantReadWriteLock();
String key = "lock";//存入 map 中的 key
Random r = new Random();//生成随机数作为 value
for (int i = 0; i <5; i++) {
//5 个线程读 map 中 key 的 value
new Thread(() -> {
for (int j = 0; j <10; j++) {
System.out.println(Thread.currentThread().getName() + " read value=" + test.get(key));
}
}).start();
//5 个线程写 map 中 key 的 value
new Thread(() -> {
for (int j = 0; j <10; j++) {
int value = r.nextInt(1000);
test.set(key, value);
System.out.println(Thread.currentThread().getName() + " write value=" + value);
}
}).start();
}
}
}
synchronized 修饰代码块时,最好不要锁定基本类型的包装类,如 jvm 会缓存 -128 ~ 127 Integer 对象,每次向如下方式定义 Integer 对象,会获得同一个 Integer,如果不同地方锁定,可能会导致诡异的性能问题或者死锁
Integer i = 100;
synchronized 修饰代码块时,要线程互斥地执行代码块,需要确保锁定的是同一个对象,这点往往在实际编程中会被忽视
synchronized 不支持尝试获取锁、锁超时和公平锁
ReentrantLock 一定要记得在 finally{} 语句块中调用 unlock() 方法释放锁,不然可能导致死锁
ReentrantLock 在并发量很高的情况,由于自旋很消耗 CPU 资源
ReentrantReadWriteLock 适合对共享资源写操作很少,读操作频繁的场景;可以从写锁降级到读锁,无法从读锁升级到写锁
2xx:表示请求已被成功接收、理解、接受
3xx:重定向
4xx:客户端错误,请求有语法错误或请求无法实现
5xx:服务器端错误,无法处理请求
答案:C
分析:
对 Java 语言来说,一切皆是对象。
对象有以下特点:
面向对象的特性:
先解释两个概念。
区别:
notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。
测试代码
public class TestNotifyNotifyAll {
private static Object obj = new Object();
public static void main(String[] args) {
//测试 RunnableImplA wait()
Thread t1 = new Thread(new RunnableImplA(obj));
Thread t2 = new Thread(new RunnableImplA(obj));
t1.start();
t2.start();
//RunnableImplB notify()
Thread t3 = new Thread(new RunnableImplB(obj));
t3.start();
// //RunnableImplC notifyAll()
// Thread t4 = new Thread(new RunnableImplC(obj));
// t4.start();
}
}
class RunnableImplA implements Runnable {
private Object obj;
public RunnableImplA(Object obj) {
this.obj = obj;
}
public void run() {
System.out.println("run on RunnableImplA");
synchronized (obj) {
System.out.println("obj to wait on RunnableImplA");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("obj continue to run on RunnableImplA");
}
}
}
class RunnableImplB implements Runnable {
private Object obj;
public RunnableImplB(Object obj) {
this.obj = obj;
}
public void run() {
System.out.println("run on RunnableImplB");
System.out.println("睡眠3秒...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("notify obj on RunnableImplB");
obj.notify();
}
}
}
class RunnableImplC implements Runnable {
private Object obj;
public RunnableImplC(Object obj) {
this.obj = obj;
}
public void run() {
System.out.println("run on RunnableImplC");
System.out.println("睡眠3秒...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("notifyAll obj on RunnableImplC");
obj.notifyAll();
}
}
}
结果:仅调用一次 obj.notify(),线程 t1 或 t2 中的一个始终在等待被唤醒,程序不终止
run on RunnableImplA
obj to wait on RunnableImplA
run on RunnableImplA
obj to wait on RunnableImplA
run on RunnableImplB
睡眠3秒...
notify obj on RunnableImplB
obj continue to run on RunnableImplA
把 t3 注掉,启动 t4 线程。调用 obj.notifyAll() 方法
public class TestNotifyNotifyAll {
private static Object obj = new Object();
public static void main(String[] args) {
//测试 RunnableImplA wait()
Thread t1 = new Thread(new RunnableImplA(obj));
Thread t2 = new Thread(new RunnableImplA(obj));
t1.start();
t2.start();
// //RunnableImplB notify()
// Thread t3 = new Thread(new RunnableImplB(obj));
// t3.start();
//RunnableImplC notifyAll()
Thread t4 = new Thread(new RunnableImplC(obj));
t4.start();
}
}
结果:t1、t2线程均可以执行完毕
run on RunnableImplA
obj to wait on RunnableImplA
run on RunnableImplA
obj to wait on RunnableImplA
run on RunnableImplC
睡眠3秒...
notifyAll obj on RunnableImplC
obj continue to run on RunnableImplA
obj continue to run on RunnableImplA
计算机不能直接理解高级语言,只能理解和运行机器语言。必须要把高级语言翻译成机器语言,计算机才能运行高级语言所编写的程序。
翻译的方式有两种,一个是编译,一个是解释。
用编译型语言写的程序执行之前,需要一个专门的编译过程,通过编译系统把高级语言翻译成机器语言,把源高级程序编译成为机器语言文件,以后直接运行而不需要再编译了,所以一般编译型语言的程序执行效率高。
解释型语言在运行的时候才解释成机器语言,每个语句都是执行时才翻译。每执行一次就要翻译一次,效率较低。
Java 是一种兼具编译和解释特性的语言,.java 文件会被编译成与平台无关的 .class 文件,但是 .class 字节码文件无法被计算机直接,仍然需要 JVM 进行翻译成机器语言。
所以严格意义上来说,Java 是一种解释型语言。
输出
1 dbc
分析:
MyBatis 是一款优秀的持久层框架。
ps: 摘自官网
https://mybatis.org/mybatis-3/zh/index.html
数值型函数
字符串函数
日期和时间函数
聚合函数
流程控制函数
并发:
并发编程:
BIO:线程发起 IO 请求,不管内核是否准备好 IO 操作,从发起请求起,线程一直阻塞,直到操作完成。
NIO:客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。
AIO:线程发起 IO 请求,立即返回;内存做好 IO 操作的准备之后,做 IO 操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做 IO 操作完成或者失败。
BIO 是一个连接一个线程。
NIO 是一个请求一个线程。
AIO 是一个有效请求一个线程。
BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的 IO 请求都是由 OS 先完成了再通知服务器应用去启动线程进行处理。
举个例子
内容摘自:https://blog.csdn.net/u013068377/article/details/70312551
结合代码可参考:https://www.cnblogs.com/barrywxx/p/8430790.html
1、整数类型: TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT
分别占用 1 字节、2 字节、3 字节、4 字节、8 字节;任何整数类型都可以加上 UNSIGNED 属性,表示数据是无符号的,即非负整数;整数类型可以被指定长度,即为显示长度,不影响存储占用空间
2、实数类型: FLOAT、DOUBLE、DECIMAL
DECIMAL 可以用于存储比 BIGINT 还大的整型,能存储精确的小数;FLOAT 和 DOUBLE 有取值范围,支持使用标准的浮点进行近似计算
3、字符串类型: CHAR、VARCHAR、TEXT、BLOB
CHAR 是定长的,根据定义的字符串长度分配足够的空间;VARCHAR 用于存储可变长字符串;TEXT 存大文本;BLOB 存二进制数据
4、枚举类型:ENUM
把不重复的数据存储为一个预定义的集合,可以替代常用的字符串类型
5、日期和时间类型:YEAR、TIME、DATE、TIMESTAMP、DATETIME
分别占用 1 byte、3 bytes、4 bytes、4 bytes、8 bytes
答案:A
分析:
如果你在企业的项目中用过 Struts2 框架,那说明你搞 Java 可能在 5 年以上了。
在 Spring MVC 火之前,Struts2 + Spring + Hibernate 就是传说中的 SSH 框架,也有 Struts2 + Spring + MyBatis 即 SSM。后来渐渐就演化到 Spring + SpringMVC + MyBatis 成为了主流。再后来大家就都知道了。
Spring 成为后端开发框架的标准早已是事实。使用 Spring 最大的好处它的 IoC 和 AOP 功能,项目中一般通过 xml 配置文件 + 注解的方式,把 Bean 的管理交给 Spring 的 IoC 容器;日志、统计耗时次数、事务管理都交由 AOP 实现,xml 和 注解申明的方式都会使用到。
Spring MVC 也基本是必用的,通过 web.xml 的配置、@Controller、@Service、@Repository,完成 http 请求到数据库的 crud 再到 view 层展示,整个调用链。其中还要配置对象转 json 的 Converter、登录拦截器、文件上传大小限制、数据源及连接池相关等等…
Spring Boot、Spring Cloud 都是基于 Spring Framework 和 Spring MVC 进一步衍生出来的。
Redis 提供两种持久化机制: RDB 和 AOF
RDBRedis DataBase:
指用数据集快照的方式半持久化模式,记录 redis 数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,可恢复数据
优点:
缺点:
AOFAppend-only file
指所有的命令行记录以 redis 命令请求协议的格式完全持久化存储,保存为 aof 文件
优点:
缺点:
类加载器实例化时进行的操作步骤:
加载 -> 连接 -> 初始化
动态代理:在运行时,创建目标类,可以调用和扩展目标类的方法。
Java 中实现动态的方式:
应用场景:
要点 1、Mapper 接口与 XML 文件的绑定是通过 XML 里 mapper 标签的 namespace 值与 Mapper 接口的 包路径.接口名 进行绑定
源码体现在 XMLMapperBuilder 的 bindMapperForNamespace 方法
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
if (boundType != null && !configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
要点 2、Mapper 接口的方法名与 XML 文件中的 sql、select、insert、update、delete 标签的 id 参数值进行绑定
源码体现在两个部分
1)生成 id 与 MappedStatement 对象注册到 configuration
XMLMapperBuilder configurationElement 方法中
//sql标签
sqlElement(context.evalNodes("/mapper/sql"));
//select、insert、update、delete标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
XMLMapperBuilder sqlElement 方法中
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
XMLStatementBuilder parseStatementNode 方法中
//获取 Mapper xml 中标签 id
String id = context.getStringAttribute("id");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
MapperBuilderAssistant addMappedStatement 方法中
id = applyCurrentNamespace(id, false);
MapperBuilderAssistant applyCurrentNamespace 方法中
return currentNamespace + "." + base;
MapperBuilderAssistant addMappedStatement 方法中,最后把 MappedStatement 注册到 configuration 对象中
configuration.addMappedStatement(statement);
2)根据 Mapper 接口方法查到并调用对应的 MappedStatement,完成绑定
MapperProxy cachedInvoker 方法创建 PlainMethodInvoker 对象,创建了 MapperMethod 对象
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
MapperMethod 对象的 SqlCommand 中的 name 属性根据解析设置为对应的 MappedStatement 的 id
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration);
name = ms.getId();
MapperMethod execute 方法 SqlCommand 类型,通过 sqlSession 根据 SqlCommand 的 name(上一步被设置为 对应的 MappedStatement 的 id) 找到 MappedStatement 执行 select、insert、update、delete
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
.
.
.
return result;
}
}
Just In Time Compiler 的简称,即时编译器。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器就是 JIT。
方式一:在配置文件 mybatis-config.xml 中添加及其子标签,编写对应的 Mapper 接口与 XML
方式二、硬编码方式在 configuration 对象中注册 Mapper 接口
//配置
Configuration configuration = new Configuration(environment);
//注册
configuration.addMapper(UserMapper.class);
JDK 1.8 中 HashMap 和 Hashtable 主要区别如下:
深入的细节,可以参考:
实现思路与注意事项:
设置合理的过期时间,解决忘记释放锁、甚至服务器宕机未释放锁的问题
获取锁和设置过期时间,需要具有原子性,使用指令
SET key value NX PX milliseconds
NX 代表只有当键key不存在的时候才会设置key的值
PX 表示设置键 key 的过期时间,单位是毫秒
value 值随机设置,删除 value 前判断是否相等,解决当前线程可能释放其他线程加的锁的问题
lua 脚本可以解决,删除 value 时判断-删除,非原子操作的问题
并发程序一旦死锁,往往我们只能重启应用。解决死锁问题最好的办法就是避免死锁。
死锁发生的条件
避免死锁的方法
对于以上 4 个条件,只要破坏其中一个条件,就可以避免死锁的发生。
对于第一个条件 “互斥” 是不能破坏的,因为加锁就是为了保证互斥。
其他三个条件,我们可以尝试
编程中的最佳实践:
示例
使用管理类一次性申请所有的资源,破坏 “占有且等待” 条件示例
package constxiong.concurrency.a023;
import java.util.HashSet;
import java.util.Set;
/**
* 测试 一次性申请所有的资源,破坏 "占有且等待" 条件示例
* @author ConstXiong
* @date 2019-09-24 14:04:12
*/
public class TestBreakLockAndWait {
//单例的资源管理类
private final static Manger manager = new Manger();
//资源1
private static Object res1 = new Object();
//资源2
private static Object res2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
boolean applySuccess = false;
while (!applySuccess) {
//向管理类,申请res1和res2,申请失败,重试
applySuccess = manager.applyResources(res1, res2);
if (applySuccess) {
try {
System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
synchronized (res1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
//休眠 1秒
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (res2) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
}
}
} finally {
manager.returnResources(res1, res2);//归还资源
}
} else {
System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源失败");
//申请失败休眠 200 毫秒后重试
try {
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(() -> {
boolean applySuccess = false;
while (!applySuccess) {
//向管理类,申请res1和res2,申请失败,重试
applySuccess = manager.applyResources(res1, res2);
if (applySuccess) {
try {
System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");
synchronized (res2) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");
//休眠 1秒
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (res1) {
System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");
}
}
} finally {
manager.returnResources(res1, res2);//归还资源
}
} else {
System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源失败");
//申请失败休眠 200 毫秒后重试
try {
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
}
/**
* 资源申请、归还管理类
* @author ConstXiong
* @date 2019-09-24 14:10:57
*/
class Manger {
//资源存放集合
private Set resources = new HashSet();
/**
* 申请资源
* @param res1
* @param res2
* @return
*/
synchronized boolean applyResources(Object res1, Object res2) {
if (resources.contains(res1) || resources.contains(res1)) {
return false;
} else {
resources.add(res1);
resources.add(res2);
return true;
}
}
/**
* 归还资源
* @param res1
* @param res2
*/
synchronized void returnResources(Object res1, Object res2) {
resources.remove(res1);
resources.remove(res2);
}
}
打印结果如下,线程-1 在线程-0 释放完资源后才能成功申请 res1 和 res2 的锁
线程:Thread-0 申请 res1、res2 资源成功
线程:Thread-0 获取到 res1 资源的锁
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-0 获取到 res2 资源的锁
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源成功
线程:Thread-1 获取到 res1 资源的锁
线程:Thread-1 获取到 res2 资源的锁
使用 Lock 的 tryLock() 方法,获取锁失败释放所有资源,破坏 “不可抢占” 条件示例
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();
}
}
打印结果如下
线程:Thread-0 获取锁 lock1 成功
线程:Thread-1 获取锁 lock2 成功
线程:Thread-1 获取锁 lock1 失败
线程:Thread-1 获取锁 lock2 成功
线程:Thread-0 获取锁 lock2 失败
线程:Thread-1 获取锁 lock1 成功
线程:Thread-0 获取锁 lock1 成功
线程:Thread-0 获取锁 lock2 成功
按照一定的顺序加锁,破坏 “循环等待” 条件示例
package constxiong.concurrency.a023;
/**
* 测试 按序申请资源,破坏 "循环等待" 条件
* @author ConstXiong
* @date 2019-09-24 15:26:23
*/
public class TestBreakLockCircleWait {
private static Object res1 = new Object();
private static Object res2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
Object first = res1;
Object second = res2;
//比较 res1 和 res2 的 hashCode,如果 res1 的 hashcode > res2,交换 first 和 second。保证 hashCode 小的对象先加锁
if (res1.hashCode() > res2.hashCode()) {
first = res2;
second = res1;
}
synchronized (first) {
System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + first + " 锁成功");
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
synchronized(second) {
System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + second + " 锁成功");
}
}
}).start();
new Thread(() -> {
Object first = res1;
Object second = res2;
//比较 res1 和 res2 的 hashCode,如果 res1 的 hashcode > res2,交换 first 和 second。保证 hashCode 小的对象先加锁
if (res1.hashCode() > res2.hashCode()) {
first = res2;
second = res1;
}
synchronized (first) {
System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + first + " 锁成功");
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
synchronized(second) {
System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + second + " 锁成功");
}
}
}).start();
}
}
打印结果如下
线程:Thread-0获取资源 java.lang.Object@7447157c 锁成功
线程:Thread-0获取资源 java.lang.Object@7a80f45c 锁成功
线程:Thread-1获取资源 java.lang.Object@7447157c 锁成功
线程:Thread-1获取资源 java.lang.Object@7a80f45c 锁成功
1、修改 SQL,给查询字段重命名,如 将 user_id 重命名为 userId
select user_id as userId from table
2、MyBatis 的 XML 映射文件中,使用 标签,定义数据库字段名与实体 bean 的属性字段名的映射关系
阅读命令,与 cat 类似, more 会以一页一页的显示方便逐页阅读,按空格键(space)就往下一页显示,按 b 键就会往回(back)一页显示
命令参数:
+n 从笫 n 行开始显示
-n 定义屏幕大小为n行
+/pattern 在每个档案显示前搜寻该字串(pattern),然后从该字串前两行之后开始显示
-c 从顶部清屏,然后显示
-d 提示“Press space to continue,’q’ to quit(按空格键继续,按q键退出)”,禁用响铃功能
-l 忽略Ctrl+l(换页)字符
-p 通过清除窗口而不是滚屏来对文件进行换页,与-c选项相似
-s 把连续的多个空行显示为一行
-u 把文件内容中的下画线去掉
常用操作命令:
Enter 向下 n 行,需要定义。默认为 1 行
Ctrl+F 向下滚动一屏
空格键 向下滚动一屏
Ctrl+B 返回上一屏
= 输出当前行的行号
:f 输出文件名和当前行的行号
V 调用vi编辑器
!命令 调用Shell,并执行命令
q 退出more
more +3 text.txt 显示文件中从第3行起的内容
ls -l | more -5 在所列出文件目录详细信息,每次显示 5 行
getBean 的默认实现的入口是在 AbstractBeanFactory#doGetBean 方法
结合源码来看,创建 bean 的线程安全是通过可并发容器 + 加锁 synchronized 保证的
比如列举几个可说的点:
类似之处还有很多,可结合源码进行查看
源码1:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return s
源码2:
protected RootBeanDefinition getMergedBeanDefinition(
String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
throws BeanDefinitionStoreException {
synchronized (this.mergedBeanDefinitions) {
RootBeanDefinition mbd = null;
RootBeanDefinition previous = null;
并行:指两个或两个以上事件或活动在同一时刻发生。如多个任务在多个 CPU 或 CPU 的多个核上同时执行,不存在 CPU 资源的竞争、等待行为。
并行与并发的区别
Java 中的多线程
2.8 版以前
Redis 通过同步(sync)和指令传播(command propagate)两个操作完成同步
同步(sync):将从节点的数据库状态更新至与主节点的数据库状态一致
指令传播(command propagate):主节点数据被修改,会主动向从节点发送执行的写指令,从节点执行之后,两个节点数据状态又保持一致
为了解决主从节点断线复制低效的问题(SYNC过程中生成、传输、载入 RDB 文件耗费大量 CPU、内存、磁盘 IO 资源),2.8 版开始新增 PSYNC 指令。
PSYNC 具有两种模式
不能
脏读:一个事务读取另外一个事务还没有提交的数据。
sql 1992 标准
http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
P2 (“Non-repeatable read”): SQL-transaction T1 reads a row. SQL-
transaction T2 then modifies or deletes that row and performs
a COMMIT. If T1 then attempts to reread the row, it may receive
the modified value or discover that the row has been deleted.P3 (“Phantom”): SQL-transaction T1 reads the set of rows N
that satisfy some. SQL-transaction T2 then
executes SQL-statements that generate one or more rows that
satisfy theused by SQL-transaction T1. If
SQL-transaction T1 then repeats the initial read with the same
, it obtains a different collection of rows.
**不可重复读:**事务 T1 读到某行;事务 T2 修改或删除这行,提交事务;T1 重新读取发现这行数据已经被修改或删除。
**幻读:**事务 T1 读取了 N 行;事务 T2 在事务 T1 读取的条件范围内生成了一行或多行数据;T1 重新读取获得与之前不同集合的行数据。
mysql 官网的术语解释,8.0 最新版
https://dev.mysql.com/doc/refman/8.0/en/glossary.html
non-repeatable read
The situation when a query retrieves data, and a later query within the same transaction retrieves what should be the same data, but the queries return different results (changed by another transaction committing in the meantime).phantom
A row that appears in the result set of a query, but not in the result set of an earlier query. For example, if a query is run twice within a transaction, and in the meantime, another transaction commits after inserting a new row or updating a row so that it matches the WHERE clause of the query.This occurrence is known as a phantom read. It is harder to guard against than a non-repeatable read, because locking all the rows from the first query result set does not prevent the changes that cause the phantom to appear.
**不可重复读:**一个事务内,两次相同条件的查询返回了不同的结果。
**幻读:**同一个事务中,一条数据出现在这次查询的结果集里,却没有出现在之前的查询结果集中。例如,在一个事务中进行了同一个查询运行了两次,期间被另外一个事务提交插入一行或修改查询条件匹配的一行。它比不可重复读更难防范,因为锁定第一个查询结果集的所有行并不能阻止导致幻象出现的更改。
从以上两处的定义可以看出
影响因素 | ||
sql 1992 标准 | mysql 术语解释 | |
不可重复读 | 其他事务修改或删除 | 未明确不同结果的原因 |
幻读 | 新增一行或多行 | 新增或修改 |
不同出处的规定存在细微差别,并非完全统一。
PS:
SQL 注入是在编译的过程中,注入了某些特殊的恶意 SQL 片段,被编译成了恶意的 SQL 执行操作。
预编译是提前对 SQL 进行编译,后面注入的参数不会对 SQL 的结构产生影响,从而避免安全风险。
用于改变 linux 系统文件或目录的访问权限
该命令有两种用法:一种是包含字母和操作符表达式的文字设定法;另一种是包含数字的数字设定法
每一文件或目录的访问权限都有三组,每组用三位代号表示:
文件属主的读、写和执行权限
与属主同组的用户的读、写和执行权限
系统中其他用户的读、写和执行权限
常用参数:
-c 当发生改变时,报告处理信息
-R 处理指定目录以及其子目录下所有文件
权限范围:
u:目录或者文件的当前的用户
g:目录或者文件的当前的群组
o:除了目录或者文件的当前用户或群组之外的用户或者群组
a:所有的用户及群组
权限代号:
r:读权限,用数字4表示
w:写权限,用数字2表示
x:执行权限,用数字1表示
-:删除权限,用数字0表示
s:特殊权限
常用的两种方式:DOM 和 SAX
选择:
1、什么是 session
session 是浏览器和服务器会话过程中,服务器分配的一块储存空间。服务器默认为浏览器在cookie中设置 sessionid,浏览器在向服务器请求过程中传输 cookie 包含 sessionid ,服务器根据 sessionid 获取出会话中存储的信息。
由于 http 协议是无状态的,即 http 请求一次连接一次,数据传输完毕,连接就断开了,下次访问需要重新连接。
通过 cookie 中的 sessionid 字段和服务器端的 session 关联,可以确定会话的身份信息。
2、session 比 cookie 更安全
用户信息可以通过加密存储到 cookie,但是这样做的安全性很差,浏览器的 cookie 的容易被其他程序获取和篡改。使用 session 的意义在于 session 存储在服务器,相对安全性更高。
3、session 的生命周期
浏览器访问服务器的 servlet(jsp)时,服务器会自动创建 session,并把 sessionid 通过 cookie 返回到浏览器。
servlet 规范中,通过 request.getSession(true) 可以强制创建 session。
服务器会默认给 session 一个过期时间,即从该 session 的会话在有效时间内没有再被访问就会被设置过超时,需要重新建立会话。
如 tomcat 的默认会话超时时间为30分钟。
会话超时时间是可以通过配置文件设置,如修改 web.xml 、server.xml 文件
1、web.xml 文件
30
2、server.xml 文件
调用 servlet api 手动设置 session 超时时间
request.getSession().setMaxInactiveInterval(60 * 30);//session 30分钟失效
调用 servlet api 手动销毁 session
request.getSession().invalidate();
4、注意事项
如果浏览器禁用 cookie,默认情况下 session 无法生效。可以通过url重载携带 sessionid 参数、把 sessionid 设置为 http 协议 header 设为其他自定义字段中,请求中始终携带。
当用户量很大、 session 的失效时间很长,需要注意 session 的查找和存储对服务器性能的影响。
web 容器可以设置 session 的钝化(从内存持久化到文件) 和 活化(从文件读到内存),提高性能。
复制,将多文件或目录复制至目标目录(shell 脚本中不加 -i 参数会直接覆盖不会提示)
常用命令:
-i 提示
-r 复制目录及目录内所有项目
-a 复制的文件与原文件时间一样
cp -ai a.txt test 复制 a.txt 到 test 目录下,保持原文件时间,如果原文件存在提示是否覆盖。
cp -s a.txt a_link.txt 为 a.txt 文件创建一个链接
不能
查找文件树命令,用于在文件树中查找文件,并作出相应的处理。
命令格式:find pathname -options [-print -exec -ok ...]
命令参数:
pathname: 查找的目录路径
-print: 匹配的文件输出到标准输出
-exec: 对匹配的文件执行该参数所给出的 shell 命令
-ok: 和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的 shell 命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行
命令选项:
-name 按照文件名查找文件
-perm 按文件权限查找文件
-user 按文件属主查找文件
-group 按照文件所属的组来查找文件。
-type 查找某一类型的文件
b - 块设备文件
d - 目录
c - 字符设备文件
l - 符号链接文件
p - 管道文件
f - 普通文件
-size n :[c] 查找文件长度为n块文件,带有 c 时表文件字节大小
-amin n 查找系统中最后 n 分钟访问的文件
-atime n 查找系统中最后 n*24小时访问的文件
-cmin n 查找系统中最后 n 分钟被改变文件状态的文件
-ctime n 查找系统中最后 n*24小时被改变文件状态的文件
-mmin n 查找系统中最后 n 分钟被改变文件数据的文件
-mtime n 查找系统中最后 n*24 小时被改变文件数据的文件,用减号 - 来限定更改时间在距今 n 日以内的文件,而用加号 + 来限定更改时间在距今n日以前的文件
-maxdepth n 最大查找目录深度
-prune 选项来指出需要忽略的目录
-newer 查找更改时间比某个文件新但比另一个文件旧的所有文件
== 作用:
equals()方法的作用:
把各种不同的异常进行分类
每个异常都是一个对象,是 Throwable 或其子类的实例
一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用对象的方法可以捕获到这个异常并进行处理
Java 中的异常处理通过 5 个关键词实现:throw、throws、try、catch 和 finally
定义方法时,可以使用 throws 关键字抛出异常
方法体内使用 throw 抛出异常
使用 try 执行一段代码,当出现异常后,停止后续代码的执行,跳至 catch 语句块
使用 catch 来捕获指定的异常,并进行处理
finally 语句块表示的语义是在 try、catch 语句块执行结束后,最后一定会被执行
单例模式:
一个类只允许创建一个实例对象,并提供访问其唯一的对象的方式。这个类就是一个单例类,这种设计模式叫作单例模式。
作用:
避免频繁创建和销毁系统全局使用的对象。
单例模式的特点:
应用场景:
饿汉式与懒汉式的区别:
线程安全性问题:
饿汉式,在被调用 getInstance() 方法时,单例已经由 jvm 加载初始化完成,所以并发访问 getInstance() 方法返回的都是同一实例对象,线程安全。
懒汉式,要保证线程安全,可以有以下几种方式:
示例代码:
1、饿汉式
package constxiong.interview;
/**
* 单例模式 饿汉式
* @author ConstXiong
*/
public class TestSingleton {
private static final TestSingleton instance = new TestSingleton();
private TestSingleton() {
}
public static TestSingleton getInstance() {
return instance;
}
}
2、懒汉式:线程不安全
package constxiong.interview;
/**
* 单例模式 懒汉式-线程不安全
* @author ConstXiong
*/
public class TestSingleton {
private static TestSingleton instance;
private TestSingleton() {
}
public static TestSingleton getInstance() {
if (instance == null) {
instance = new TestSingleton();
}
return instance;
}
}
3、懒汉式:getInstance() 方法加锁,线程安全,性能差
package constxiong.interview;
/**
* 单例模式 懒汉式-加锁
* @author ConstXiong
*/
public class TestSingleton {
private static volatile TestSingleton instance;
private TestSingleton() {
}
public static synchronized TestSingleton getInstance() {
if (instance == null) {
instance = new TestSingleton();
}
return instance;
}
}
4、懒汉式:双重检查 + 对类加锁
package constxiong.interview;
/**
* 单例模式 懒汉式-双重检查 + 对类加锁
* @author ConstXiong
*/
public class TestSingleton {
private static volatile TestSingleton instance;
private TestSingleton() {
}
public static TestSingleton getInstance() {
if (instance == null) {
synchronized (TestSingleton.class) {
if (instance == null) {
instance = new TestSingleton();
}
}
}
return instance;
}
}
5、懒汉式:静态内部类
package constxiong.interview;
/**
* 单例模式 懒汉式-静态内部类
* @author ConstXiong
*/
public class TestSingleton {
private static class SingletonHolder {
private static final TestSingleton instance = new TestSingleton();
}
private TestSingleton() {
}
public static TestSingleton getInstance() {
return SingletonHolder.instance;
}
}
6、懒汉式:枚举
package constxiong.interview;
import java.util.concurrent.atomic.AtomicLong;
/**
* 单例模式 懒汉式-枚举,id生成器
* @author ConstXiong
*/
public enum TestSingleton {
INSTANCE;
private AtomicLong id = new AtomicLong(0);
public long getId() {
return id.incrementAndGet();
}
}
实现方式的选择建议:
作用:
SqlSession 级别的缓存,默认开启,在 MyBatis 配置文件中可以修改 MyBatis 文件中 标签 localCacheScope 参数值改变缓存的作用域。statementId、boundSql.getSql() 执行 sql、查询参数、RowBounds 都相同,即认为是同一次查询,返回缓存值。
实现原理:
每个 SqlSession 对象包含一个 Executor 对象,Executor 对象中 localCache 属性使用 PerpetualCache 对象缓存查询数据;从源码中看 DefaultSqlSession 的 close、commit、rollback、insert、delete、update 相关的方法都会触发 BaseExecutor 对象清掉缓存。
作用:
MappedStatement 级别的缓存,默认不开启,可以在 Mapper xml 中通过 标签开启 或者 MyBatis 文件中 标签设置 cacheEnabled 参数为 true 全局开启 或者 mapper xml 配置文件中的 select 节点需要加上属性 useCache,在 SqlSession 关闭或提交之后才会生效。
开启二级缓存的默认作用摘自官网
实现原理:
这个跟 Redis 的内存回收策略有关。
Redis 的默认回收策略是 noenviction,当内存用完之后,写数据会报错。
Redis 的其他内存回收策略含义:
this:
super:
Java 容器分为 Collection 和 Map 两大类,各自都有很多子类。
Collection
| ├AbstractCollection 对Collection接口的最小化抽象实现
| │
| ├List 有序集合
| │-├AbstractList 有序集合的最小化抽象实现
| │-├ArrayList 基于数组实现的有序集合
| │-├LinkedList 基于链表实现的有序集合
| │-└Vector 矢量队列
| │ └Stack 栈,先进后出
| │
| ├Set 不重复集合
| │├AbstractSet 不重复集合的最小化抽象实现
| │├HashSet 基于hash实现的不重复集合,无序
| │├LinkedHashSet 基于hash实现的不重复集合,有序
| │└SortedSet 可排序不重复集合
| │ └NavigableSet 可导航搜索的不重复集合
| │ └TreeSet 基于红黑树实现的可排序不重复集合
| │
| ├Queue 队列
| │├AbstractQueue 队列的核心实现
| │├BlockingQueue 阻塞队列
| │└Deque 可两端操作线性集合
|
Map 键值映射集合
| ├AbstractMap 键值映射集合最小化抽象实现
| ├Hashtable 基于哈希表实现的键值映射集合,key、value均不可为null
| ├HashMap 类似Hashtable,但方法不同步,key、value可为null
| └LinkedHashMap 根据插入顺序实现的键值映射集合
| ├IdentityHashMap 基于哈希表实现的键值映射集合,两个key引用相等==,认为是同一个key
| ├SortedMap 可排序键值映射集合
| └NavigableMap 可导航搜索的键值映射集合
| └WeakHashMap 弱引用建,不阻塞被垃圾回收器回收,key回收后自动移除键值对
可以比较的点:
可以通讯
答案:AD
分析:
ps(process status),用来查看当前运行的进程状态,一次性查看,如果需要动态连续结果使用 top 指令
linux 系统中进程有5种状态:
ps 工具标识进程的5种状态码:
R 运行 runnable
S 中断 sleeping
D 不可中断 uninterruptible sleep
Z 僵死 a defunct process
T 停止 traced or stopped
常用参数:
-A 显示所有进程
-a 显示同一终端下所有进程
-f: full 展示进程详细信息
-e: every 展示所有进程信息
-ax: all 与 -e 同,展示所有进程信息
-o: 设置输出格式, 可以指定需要输出的进程信息列
-L: 展示线程信息
-C: 获取指定命令名的进程信息
-t: tty 展示关联指定 tty 的进程
–forest: 展示进程数
–sort: 按照某个或者某些进程信息列排序展示
a 显示所有进程
c 显示进程真实名称
e 显示环境变量
f 显示进程间的关系
r 显示当前终端运行的进程
-aux 显示所有包含其它使用的进程
-ef 显示所有当前进程信息
ps -C bash 显示指定名称的进程信息
ps -eLf 显示当前系统中的线程信息
ps -ef --forest 显示进程树
Dubbo 框架设计一共划分了 10 层:
将指定文件的拥有者改为指定的用户或组
用户可以是用户名或者用户 ID
组可以是组名或者组 ID
文件是以空格分开的要改变权限的文件列表,支持通配符
常用参数:
-c 显示更改的部分的信息
-R 处理指定目录及子目录
示例
chown -c log:log log.log 改变文件 log.log 的拥有者和群组都为 log 并显示改变信息
chown -c :log log.log 改变文件 log.log 的群组为 log
chown -cR log: log/ 改变文件夹 log 及子文件、目录属主 log
J2SE:Standard Edition(标准版) ,包含 Java 语言的核心类。如IO、JDBC、工具类、网络编程相关类等。从JDK 5.0开始,改名为Java SE。
J2EE:Enterprise Edition(企业版),包含 J2SE 中的类和企业级应用开发的类。如web相关的servlet类、JSP、xml生成与解析的类等。从JDK 5.0开始,改名为Java EE。
J2ME:Micro Edition(微型版),包含 J2SE 中的部分类,新添加了一些专有类。一般用设备的嵌入式开发,如手机、机顶盒等。从JDK 5.0开始,改名为Java ME。
Redis存储在内存中的数据升到配置大小时,就进行数据淘汰
使用 allkeys-lru 策略,从数据集(server.db[i].dict)中挑选最近最少使用的数据优先淘汰,即可满足保存热点数据
切换目录,changeDirectory 的缩写
命令语法:cd [目录名]
cd / 进入要目录
cd ~ 进入 "home" 目录
cd - 进入上一次工作路径
cd !$ 把上个命令的参数作为cd参数使用
Oracle中使用 || 这个符号连接字符串
如 ‘Const’ || ‘Xiong’
注:
JDK1.8 源码中的解释
/**
* Inserts the specified element into this queue if it is possible to do so
* immediately without violating capacity restrictions, returning
* {@code true} upon success and throwing an {@code IllegalStateException}
* if no space is currently available.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Collection#add})
* @throws IllegalStateException if the element cannot be added at this
* time due to capacity restrictions
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this queue
* @throws NullPointerException if the specified element is null and
* this queue does not permit null elements
* @throws IllegalArgumentException if some property of this element
* prevents it from being added to this queue
*/
boolean add(E e);
/**
* Inserts the specified element into this queue if it is possible to do
* so immediately without violating capacity restrictions.
* When using a capacity-restricted queue, this method is generally
* preferable to {@link #add}, which can fail to insert an element only
* by throwing an exception.
*
* @param e the element to add
* @return {@code true} if the element was added to this queue, else
* {@code false}
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this queue
* @throws NullPointerException if the specified element is null and
* this queue does not permit null elements
* @throws IllegalArgumentException if some property of this element
* prevents it from being added to this queue
*/
boolean offer(E e);
答案:BC
分析:
垃圾回收机制,简称 GC
特点
答案:B
分析:
是由一个工厂对象创建产品实例,简单工厂模式的工厂类一般是使用静态方法,通过不同的参数的创建不同的对象实例
可以生产结构中的任意产品,不能增加新的产品
提供一个创建一系列相关或相互依赖对象的接口,而无需制定他们具体的类,生产多个系列产品
生产不同产品族的全部产品,不能新增产品,可以新增产品族
Advice 的 执行顺序:
Inner Class:内部类
内部类就是在一个类的内部定义的类
内部类中不能定义静态成员
内部类可以直接访问外部类中的成员变量
内部类可以定义在外部类的方法外面,也可以定义在外部类的方法体中
在方法体外面定义的内部类的访问类型可以是 public, protected , 默认的,private 等 4 种类型
方法内部定义的内部类前面不能有访问类型修饰符,可以使用 final 或 abstract 修饰符
创建内部类的实例对象时,一定要先创建外部类的实例对象,然后用这个外部类的实例对象去创建内部类的实例对象
内部类里还包含匿名内部类,即直接对类或接口的方法进行实现,不用单独去定义内部类
//内部类的创建语法
Outer outer = new Outer();
Outer.Inner inner = outer.new Innner();
Static Nested Class:静态嵌套类
不依赖于外部类的实例对象
不能直接访问外部类的非 static 成员变量
可以直接引用外部类的static的成员变量,不需要加上外部类的名字
在静态方法中定义的内部类也是Static Nested Class
//静态内部类创建语法
Outter.Inner inner = new Outter.Inner();
官方给出,理论值是 2 的 32 次方个
实际使用中单个 Redis 实例最小储存 2.5 亿个 key
JavaScript 与 Java 是两个公司开发的不同的两个产品。
区别:
使用 ping 指令,如:
redis-cli -h host -p port -a password
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
Java 代码对 Redis 连通性测试,可以使用 Redis 客户端类库包里的 api 发送 ping 指令
//连接redis
Jedis jedis=new Jedis("127.0.0.1",6379);
//查看服务器是否运行,打出 pong 表示OK
System.out.println("ping redis:" + jedis.ping());
让A、B成为父子类,C继承子类即可。
按流的处理位置分类
区别:
特征:
常见的字节流:
常见的字符流:
MQ(Message Queue)消息队列,是 “先进先出” 的一种数据结构。
MQ 的作用:一般用来解决应用解耦,异步处理,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性架构。
当 A 系统生产关键数据,发送数据给多个其他系统消费,此时 A 系统和其他系统产生了严重的耦合,如果将 A 系统产生的数据放到 MQ 当中,其他系统去 MQ 获取消费数据,此时各系统独立运行只与 MQ 交互,添加新系统消费 A 系统的数据也不需要去修改 A 系统的代码,达到了解耦的效果。
互联网类企业对用户的直接操作,一般要求每个请求在 200ms 以内完成。对于一个系统调用多个系统,不使用 MQ 的情况下,它执行完返回的耗时是调用完所有系统所需时间的总和;使用 MQ 进行优化后,执行的耗时则是执行主系统的耗时加上发送数据到消息队列的耗时,大幅度提升系统性能和用户体验。
MySQL 每秒最高并发请求在 2000 左右,用户访问量高峰期的时候涌入的大量请求,会将 MySQL 打死,然后系统就挂掉,但过了高峰期,请求量可能远低于 2000,这种情况去增加服务器就不值得,如果使用 MQ 的情况,将用户的请求全部放到 MQ 中,让系统去消费用户的请求,不要超过系统所能承受的最大请求数量,保证系统不会再高峰期挂掉,高峰期过后系统还是按照最大请求数量处理完请求。
1、实现 Cloneable 接口,重写 clone() 方法。
2、不实现 Cloneable 接口,会报 CloneNotSupportedException 异常。
package constxiong.interview;
/**
* 测试克隆
* @author ConstXiong
* @date 2019-06-18 11:21:21
*/
public class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person(1, "ConstXiong");//创建对象 Person p1
Person p2 = (Person)p1.clone();//克隆对象 p1
p2.setName("其不答");//修改 p2的name属性,p1的name未变
System.out.println(p1);
System.out.println(p2);
}
}
/**
* 人
* @author ConstXiong
* @date 2019-06-18 11:54:35
*/
class Person implements Cloneable {
private int pid;
private String name;
public Person(int pid, String name) {
this.pid = pid;
this.name = name;
System.out.println("Person constructor call");
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person [pid:"+pid+", name:"+name+"]";
}
}
打印结果
Person constructor call
Person [pid:1, name:ConstXiong]
Person [pid:1, name:其不答]
3、Object 的 clone() 方法是浅拷贝,即如果类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象。
可以使用下面的两种方法,完成 Person 对象的深拷贝。
方法1、对象的属性的Class 也实现 Cloneable 接口,在克隆对象时也手动克隆属性。
@Override
public Object clone() throws CloneNotSupportedException {
DPerson p = (DPerson)super.clone();
p.setFood((DFood)p.getFood().clone());
return p;
}
完整代码
package constxiong.interview;
/**
* 测试克隆
* @author ConstXiong
* @date 2019-06-18 11:21:21
*/
public class TestManalDeepClone {
public static void main(String[] args) throws Exception {
DPerson p1 = new DPerson(1, "ConstXiong", new DFood("米饭"));//创建Person 对象 p1
DPerson p2 = (DPerson)p1.clone();//克隆p1
p2.setName("其不答");//修改p2的name属性
p2.getFood().setName("面条");//修改p2的自定义引用类型 food 属性
System.out.println(p1);//修改p2的自定义引用类型 food 属性被改变,p1的自定义引用类型 food 属性也随之改变,说明p2的food属性,只拷贝了引用,没有拷贝food对象
System.out.println(p2);
}
}
class DPerson implements Cloneable {
private int pid;
private String name;
private DFood food;
public DPerson(int pid, String name, DFood food) {
this.pid = pid;
this.name = name;
this.food = food;
System.out.println("Person constructor call");
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
DPerson p = (DPerson)super.clone();
p.setFood((DFood)p.getFood().clone());
return p;
}
@Override
public String toString() {
return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
}
public DFood getFood() {
return food;
}
public void setFood(DFood food) {
this.food = food;
}
}
class DFood implements Cloneable{
private String name;
public DFood(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
打印结果
Person constructor call
Person [pid:1, name:ConstXiong, food:米饭]
Person [pid:1, name:其不答, food:面条]
方法2、结合序列化(JDK java.io.Serializable 接口、JSON格式、XML格式等),完成深拷贝
结合 java.io.Serializable 接口,完成深拷贝
package constxiong.interview;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class TestSeriazableClone {
public static void main(String[] args) {
SPerson p1 = new SPerson(1, "ConstXiong", new SFood("米饭"));//创建 SPerson 对象 p1
SPerson p2 = (SPerson)p1.cloneBySerializable();//克隆 p1
p2.setName("其不答");//修改 p2 的 name 属性
p2.getFood().setName("面条");//修改 p2 的自定义引用类型 food 属性
System.out.println(p1);//修改 p2 的自定义引用类型 food 属性被改变,p1的自定义引用类型 food 属性未随之改变,说明p2的food属性,只拷贝了引用和 food 对象
System.out.println(p2);
}
}
class SPerson implements Cloneable, Serializable {
private static final long serialVersionUID = -7710144514831611031L;
private int pid;
private String name;
private SFood food;
public SPerson(int pid, String name, SFood food) {
this.pid = pid;
this.name = name;
this.food = food;
System.out.println("Person constructor call");
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 通过序列化完成克隆
* @return
*/
public Object cloneBySerializable() {
Object obj = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
obj = ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return obj;
}
@Override
public String toString() {
return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
}
public SFood getFood() {
return food;
}
public void setFood(SFood food) {
this.food = food;
}
}
class SFood implements Serializable {
private static final long serialVersionUID = -3443815804346831432L;
private String name;
public SFood(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
打印结果
Person constructor call
Person [pid:1, name:ConstXiong, food:米饭]
Person [pid:1, name:其不答, food:面条]
XML文档定义分为 Schema 和 DTD 两种形式
区别:
游标:
oracle中的游标分为隐式游标和显示游标
DML 操作和单行 SELECT 语句会使用隐式游标,如:
可以通过隐式游标的属性来了解操作的状态和结果,进而控制程序的流程。隐式游标的属性如下:
显示游标可以对查询语句(select)返回的多条记录进行处理
游标的使用步骤:
CPU、内存、IO 设备的读写速度差异巨大,表现为 CPU 的速度 > 内存的速度 > IO 设备的速度。
程序的性能瓶颈在于速度最慢的 IO 设备的读写,也就是说当涉及到 IO 设备的读写,再怎么提升 CPU 和内存的速度也是起不到提升性能的作用。
为了更好地利用 CPU 的高性能
基于以上优化,给并发编程带来了三大问题。
1、 CPU 缓存,在多核 CPU 的情况下,带来了可见性问题
可见性:一个线程对共享变量的修改,另一个线程能够立刻看到修改后的值
看下面代码,启动两个线程,一个线程当 stop 变量为 true 时,停止循环,一个线程启动就设置 stop 变量为 true。
package constxiong.concurrency.a014;
/**
* 测试可见性问题
* @author ConstXiong
*/
public class TestVisibility {
//是否停止 变量
private static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
//启动线程 1,当 stop 为 true,结束循环
new Thread(() -> {
System.out.println("线程 1 正在运行...");
while (!stop) ;
System.out.println("线程 1 终止");
}).start();
//休眠 10 毫秒
Thread.sleep(10);
//启动线程 2, 设置 stop = true
new Thread(() -> {
System.out.println("线程 2 正在运行...");
stop = true;
System.out.println("设置 stop 变量为 true.");
}).start();
}
}
这个就是因为 CPU 缓存导致的可见性导致的问题。线程 2 设置 stop 变量为 true,线程 1 在 CPU 1上执行,读取的 CPU 1 缓存中的 stop 变量仍然为 false,线程 1 一直在循环执行。
示意如图:
可以通过 volatile、synchronized、Lock接口、Atomic 类型保障可见性。
2、操作系统对当前执行线程的切换,带来了原子性问题
原子性:一个或多个指令在 CPU 执行的过程中不被中断的特性
看下面的一段代码,线程 1 和线程 2 分别对变量 count 增加 10000,但是结果 count 的输出却不是 20000
package constxiong.concurrency.a014;
/**
* 测试原子性问题
* @author ConstXiong
*/
public class TestAtomic {
//计数变量
static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
//线程 1 给 count 加 10000
Thread t1 = new Thread(() -> {
for (int j = 0; j <10000; j++) {
count++;
}
System.out.println("thread t1 count 加 10000 结束");
});
//线程 2 给 count 加 10000
Thread t2 = new Thread(() -> {
for (int j = 0; j <10000; j++) {
count++;
}
System.out.println("thread t2 count 加 10000 结束");
});
//启动线程 1
t1.start();
//启动线程 2
t2.start();
//等待线程 1 执行完成
t1.join();
//等待线程 2 执行完成
t2.join();
//打印 count 变量
System.out.println(count);
}
}
打印结果:
thread t2 count 加 10000 结束
thread t1 count 加 10000 结束
11377
这个就是因为线程切换导致的原子性问题。
Java 代码中 的 count++ ,至少需要三条 CPU 指令:
即使是单核的 CPU,当线程 1 执行到指令 1 时发生线程切换,线程 2 从内存中读取 count 变量,此时线程 1 和线程 2 中的 count 变量值是相等,都执行完指令 2 和指令 3,写入的 count 的值是相同的。从结果上看,两个线程都进行了 count++,但是 count 的值只增加了 1。
指令执行与线程切换
3、编译器指令重排优化,带来了有序性问题
有序性:程序按照代码执行的先后顺序
看下面这段代码,复现指令重排带来的有序性问题。
package constxiong.concurrency.a014;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 测试有序性问题
* @author ConstXiong
*/
public class TestOrderliness {
static int x;//静态变量 x
static int y;//静态变量 y
public static void main(String[] args) throws InterruptedException {
Set valueSet = new HashSet();//记录出现的结果的情况
Map valueMap = new HashMap();//存储结果的键值对
//循环 1000 万次,记录可能出现的 v1 和 v2 的情况
for (int i = 0; i <10000000; i++) {
//给 x y 赋值为 0
x = 0;
y = 0;
valueMap.clear();//清除之前记录的键值对
Thread t1 = new Thread(() -> {
int v1 = y;//将 y 赋值给 v1 ----> Step1
x = 1;//设置 x 为 1 ----> Step2
valueMap.put("v1", v1);//v1 值存入 valueMap 中 ----> Step3
}) ;
Thread t2 = new Thread(() -> {
int v2 = x;//将 x 赋值给 v2 ----> Step4
y = 1;//设置 y 为 1 ----> Step5
valueMap.put("v2", v2);//v2 值存入 valueMap 中 ----> Step6
});
//启动线程 t1 t2
t1.start();
t2.start();
//等待线程 t1 t2 执行完成
t1.join();
t2.join();
//利用 Set 记录并打印 v1 和 v2 可能出现的不同结果
valueSet.add("(v1=" + valueMap.get("v1") + ",v2=" + valueMap.get("v2") + ")");
System.out.println(valueSet);
}
}
}
打印结果出现四种情况:
v1=0,v2=0 的执行顺序是 Step1 和 Step 4 先执行
v1=1,v2=0 的执行顺序是 Step5 先于 Step1 执行
v1=0,v2=1 的执行顺序是 Step2 先于 Step4 执行
v1=1,v2=1 出现的概率极低,就是因为 CPU 指令重排序造成的。Step2 被优化到 Step1 前,Step5 被优化到 Step4 前,至少需要成立一个。
指令重排,可能会发生在两个没有相互依赖关系之间的指令。
Servlet
Jsp
区别
子类构造方法的调用规则:
不可以
String 类在 JDK 中被广泛使用,为了保证正确性、安全性,String 类是用 final 修饰,不能被继承,方法不可以被重写。
dubbo 调用服务超时,默认是会重试两次的,但可能两次请求都是成功的。如果没有幂等性处理,就会产生重复数据。
使用递归复制文件夹和文件
package constxiong.interview;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 复制文件夹
* @author ConstXiong
* @date 2019-11-13 13:38:19
*/
public class TestCopyDir {
public static void main(String[] args) {
String srcPath = "E:/a";
String destPath = "E:/a_";
copyDir(srcPath, destPath);
}
/**
* 复制文件夹
* @param srcFile
* @param destFile
*/
public static void copyDir(String srcDirPath, String destDirPath) {
File srcDir = new File(srcDirPath);
if (!srcDir.exists() || !srcDir.isDirectory()) {
throw new IllegalArgumentException("参数错误");
}
File destDir = new File(destDirPath);
if (!destDir.exists()) {
destDir.mkdirs();
}
File[] files = srcDir.listFiles();
for (File f : files) {
if (f.isFile()) {
copyFile(f, new File(destDirPath, f.getName()));
} else if (f.isDirectory()) {
copyDir(srcDirPath + File.separator + f.getName(),
destDirPath + File.separator + f.getName());
}
}
}
/**
* 复制文件
* @param srcFile
* @param destFile
*/
public static void copyFile(File srcFile, File destFile) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
byte[] b = new byte[1024];
try {
bis = new BufferedInputStream(new FileInputStream(srcFile));
bos = new BufferedOutputStream(new FileOutputStream(destFile));
int len;
while ((len = bis.read(b)) > -1) {
bos.write(b, 0, len);
}
bos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
除了使用 synchronized、Lock 加锁之外,Java 中还有很多不需要加锁就可以解决并发问题的工具类
1、原子工具类
JDK 1.8 中,java.util.concurrent.atomic 包下类都是原子类,原子类都是基于 sun.misc.Unsafe 实现的。
原子性基本数据类型:AtomicBoolean、AtomicInteger、AtomicLong
原子性对象引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference
原子性数组:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
原子性对象属性更新:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
原子性累加器:DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder
修改我们之前测试原子性问题的类,使用 AtomicInteger 的简单例子
package constxiong.concurrency.a026;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 测试 原子类 AtomicInteger
*
* @author ConstXiong
*/
public class TestAtomicInteger {
// 计数变量
static volatile AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 线程 1 给 count 加 10000
Thread t1 = new Thread(() -> {
for (int j = 0; j <10000; j++) {
count.incrementAndGet();
}
System.out.println("thread t1 count 加 10000 结束");
});
// 线程 2 给 count 加 10000
Thread t2 = new Thread(() -> {
for (int j = 0; j <10000; j++) {
count.incrementAndGet();
}
System.out.println("thread t2 count 加 10000 结束");
});
// 启动线程 1
t1.start();
// 启动线程 2
t2.start();
// 等待线程 1 执行完成
t1.join();
// 等待线程 2 执行完成
t2.join();
// 打印 count 变量
System.out.println(count.get());
}
}
打印结果如预期
thread t2 count 加 10000 结束
thread t1 count 加 10000 结束
20000
2、线程本地存储
示例
package constxiong.concurrency.a026;
/**
* 测试 原子类 AtomicInteger
*
* @author ConstXiong
*/
public class TestThreadLocal {
// 线程本地存储变量
private static final ThreadLocal THREAD_LOCAL_NUM = new ThreadLocal() {
@Override
protected Integer initialValue() {//初始值
return 0;
}
};
public static void main(String[] args) {
for (int i = 0; i <3; i++) {// 启动三个线程
Thread t = new Thread() {
@Override
public void run() {
add10ByThreadLocal();
}
};
t.start();
}
}
/**
* 线程本地存储变量加 5
*/
private static void add10ByThreadLocal() {
try {
for (int i = 0; i <5; i++) {
Integer n = THREAD_LOCAL_NUM.get();
n += 1;
THREAD_LOCAL_NUM.set(n);
System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
}
} finally {
THREAD_LOCAL_NUM.remove();// 将变量移除
}
}
}
每个线程最后一个值都打印到了 5
Thread-0 : ThreadLocal num=1
Thread-2 : ThreadLocal num=1
Thread-1 : ThreadLocal num=1
Thread-2 : ThreadLocal num=2
Thread-0 : ThreadLocal num=2
Thread-2 : ThreadLocal num=3
Thread-0 : ThreadLocal num=3
Thread-1 : ThreadLocal num=2
Thread-0 : ThreadLocal num=4
Thread-2 : ThreadLocal num=4
Thread-0 : ThreadLocal num=5
Thread-1 : ThreadLocal num=3
Thread-2 : ThreadLocal num=5
Thread-1 : ThreadLocal num=4
Thread-1 : ThreadLocal num=5
3、copy-on-write
简单的 CopyOnWriteArrayList 的示例,这里只是说明 CopyOnWriteArrayList 怎么用,并且是线程安全的。这个场景并不适合使用 CopyOnWriteArrayList,因为写多读少。
package constxiong.concurrency.a026;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 测试 copy-on-write
* @author ConstXiong
*/
public class TestCopyOnWrite {
private static final Random R = new Random();
private static CopyOnWriteArrayList cowList = new CopyOnWriteArrayList();
// private static ArrayList cowList = new ArrayList();
public static void main(String[] args) throws InterruptedException {
List threadList = new ArrayList();
//启动 1000 个线程,向 cowList 添加 5 个随机整数
for (int i = 0; i <1000; i++) {
Thread t = new Thread(() -> {
for (int j = 0; j <5; j++) {
//休眠 10 毫秒,让线程同时向 cowList 添加整数,引出并发问题
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
cowList.add(R.nextInt(100));
}
}) ;
t.start();
threadList.add(t);
}
for (Thread t : threadList) {
t.join();
}
System.out.println(cowList.size());
}
}
打印结果
5000
如果把
private static CopyOnWriteArrayList cowList = new CopyOnWriteArrayList();
改为
private static ArrayList cowList = new ArrayList();
打印结果就是小于 5000 的整数了
4、其他 “Concurrent” 开头的并发工具类,如:ConcurrentHashMap、ConcurrentLinkedDeque、ConcurrentLinkedQueue…
单纯使用 volatile 关键字是不能保证线程安全的
Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展进行加载。
Dobbo扩展的 spring xml配置文件节点说明如下:
主从复制的原理思想也很简单,就是从库不断地同步主库的改动,保持与主库数据一致;应用仅在从库中读数据。
在项目中,使用读写分离本质上就是,增加数据库服务器资源 + 网络带宽,来分摊对数据库的读写请求,从而提高了性能和可用性。主从复制实现读写分离最大的缺点就是从库同步到主库的数据存在延迟,网络不好的时候,延迟比较严重。
如何实现读写分离?
在我们平时开发中,一般不会自己去控制 select 请求从从库拿 Connection,insert、delete、update 请求从主库拿 Connection。当然也有这么干,就是把读写请求按规则命名方法,然后根据方法名通过反射统一处理请求不同的库。
大部分企业在项目中是使用中间件去实现读写分离的,如 mycat、atlas、dbproxy、cetus、Sharding-JDBC…,每种中间件各有优缺点。
Sharding-JDBC 是 apache 旗下的 ShardingSphere 中的一款产品,轻量,引入 jar 即可完成读写分离的需求,可以理解为增强版的 JDBC,现在被使用的较多。
搭建项目
maven 依赖的库
org.apache.shardingsphere
sharding-jdbc-core
4.1.1
com.zaxxer
HikariCP
3.4.5
mysql
mysql-connector-java
8.0.21
获取数据源的工具类
package constxiong;
import com.zaxxer.hikari.HikariDataSource;
/**
* 获取 DataSource 工具类,使用了 Hikari 数据库连接池
*/
import javax.sql.DataSource;
public final class DataSourceUtil {
private static final int PORT = 3306;
/**
* 通过 Hikari 数据库连接池创建 DataSource
* @param ip
* @param username
* @param password
* @param dataSourceName
* @return
*/
public static DataSource createDataSource(String ip, String username, String password, String dataSourceName) {
HikariDataSource result = new HikariDataSource();
result.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
result.setJdbcUrl(String.format("jdbc:mysql://%s:%s/%s?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8", ip, PORT, dataSourceName));
result.setUsername(username);
result.setPassword(password);
return result;
}
}
测试 Sharding-JDBC 读写分离
主库:172.31.32.184
从库:172.31.32.234
观察通过 Sharding-JDBC 获取的 DataSource 是否会自动写入到主库,从库是否主动同步,从库同步数据的延迟时间
测试代码
package constxiong;
import org.apache.shardingsphere.api.config.masterslave.MasterSlaveRuleConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.MasterSlaveDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalTime;
import java.util.*;
/**
* 测试 ShardingSphere 读写分离
* 主库:172.31.32.184
* 从库:172.31.32.234
*
* 观察通过 ShardingSphere 获取的 DataSource 是否会自动写入到主库,从库是否主动同步,从库同步数据的延迟时间
*/
public class Test {
//主库 DataSource
private static DataSource dsSlave = DataSourceUtil.createDataSource("172.31.32.234", "root", "constxiong@123", "constxiong");
//从库 DataSource
private static DataSource dsMaster = DataSourceUtil.createDataSource("172.31.32.184", "root", "constxiong@123", "constxiong");
public static void main(String[] args) throws SQLException {
//启动线程打印主库与从库当前 cuser 数据量与时间,观察从库同步数据延迟
printMasterAndSlaveData();
//从 ShardingSphere 获取 DataSource,出入数据,观察插入数据的库是否为主库
DataSource ds = getMasterSlaveDataSource();
Connection conn = ds.getConnection();
Statement stt = conn.createStatement();
stt.execute("insert into cuser values(2, 'fj')");
}
/**
* 启动线程打印,两个主从库 cuser 表的信息、数据量、当前时间
* @throws SQLException
*/
private static void printMasterAndSlaveData() throws SQLException {
Connection masterConn = dsMaster.getConnection();
Connection slaveConn = dsSlave.getConnection();
new Thread(() -> {
while (true) {
try {
System.out.println("------master------" + LocalTime.now());
print(masterConn);
System.out.println("------slave------" + LocalTime.now());
print(slaveConn);
} catch (SQLException e) {
}
}
}).start();
}
private static void print(Connection conn) throws SQLException {
Statement statement = conn.createStatement();
statement.execute("select * from cuser");
ResultSet rs = statement.getResultSet();
int count = 0;
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println(id + "-" + name);
count++;
}
System.out.println("total: " + count);
}
/**
* 设置 ShardingSphere 的主从库
* @return
* @throws SQLException
*/
private static DataSource getMasterSlaveDataSource() throws SQLException {
MasterSlaveRuleConfiguration masterSlaveRuleConfig = new MasterSlaveRuleConfiguration("ds_master_slave", "ds_master", Arrays.asList("ds_slave"));
return MasterSlaveDataSourceFactory.createDataSource(createDataSourceMap(), masterSlaveRuleConfig, new Properties());
}
/**
* 用 主从库的 DataSource 构造 map
* @return
*/
private static Map createDataSourceMap() {
Map result = new HashMap<>();
result.put("ds_master", dsMaster);
result.put("ds_slave", dsSlave);
return result;
}
}
分析延迟信息
数据默认配置的情况,在内网从库同步的时间延迟,在 200ms 左右,当然这个统计是不精确的,只是看个大概情况,理论值应该是可以做毫秒级。
参考文档:
代码上传至:
JDK 中事件编程标准接口
Spring 中的事件对应的类是 ApplicationEvent,事件的处理方式如下:
1、实现 ApplicationListener 接口,可以在 onApplicationEvent 方法上处理 ApplicationEvent
2、@EventListener 注解加载事件处理的方法上
需要将 ApplicationListener 注册为 Bean 或者
通过 ConfigurableApplicationContext#addApplicationListener 方法添加
事件的发布,可以通过 ApplicationEventPublisher 发布,也可以通过
ApplicationEventMulticaster 进行广播
其中分页插件的原理是,使用 MyBatis 提供的插件接口,拦截待执行的 SQL,根据数据库种类的配置与分页参数,生成带分页 SQL 语句,执行。
使用 top 指令,服务器中 CPU 和 内存的使用情况,-H 可以按 CPU 使用率降序,-M 内存使用率降序。排除其他进程占用过高的硬件资源,对 Java 服务造成影响。
如果发现 CPU 使用过高,可以使用 top 指令查出 JVM 中占用 CPU 过高的线程,通过 jstack 找到对应的线程代码调用,排查出问题代码。
如果发现内存使用率比较高,可以 dump 出 JVM 堆内存,然后借助 MAT 进行分析,查出大对象或者占用最多的对象来自哪里,为什么会长时间占用这么多;如果 dump 出的堆内存文件正常,此时可以考虑堆外内存被大量使用导致出现问题,需要借助操作系统指令 pmap 查出进程的内存分配情况、gdb dump 出具体内存信息、perf 查看本地函数调用等。
如果 CPU 和 内存使用率都很正常,那就需要进一步开启 GC 日志,分析用户线程暂停的时间、各部分内存区域 GC 次数和时间等指标,可以借助 jstat 或可视化工具 GCeasy 等,如果问题出在 GC 上面的话,考虑是否是内存不够、根据垃圾对象的特点进行参数调优、使用更适合的垃圾收集器;分析 jstack 出来的各个线程状态。如果问题实在比较隐蔽,考虑是否可以开启 jmx,使用 visualmv 等可视化工具远程监控与分析。
泛型:
为什么要用泛型?
比如集合类使用泛型,取出和操作元素时无需进行类型转换,避免出现 java.lang.ClassCastException 异常
原因:
优点:
缺点:
判断对象是否可回收的算法有两种:
JVM 各厂商基本都是用的 Tracing GC 实现
大部分垃圾收集器遵从了分代收集(Generational Collection)理论。
针对新生代与老年代回收垃圾内存的特点,提出了 3 种不同的算法:
1、标记-清除算法(Mark-Sweep)
标记需回收对象,统一回收;或标记存活对象,回收未标记对象。
缺点:
2、标记-复制算法(Mark-Copy)
可用内存等分两块,使用其中一块 A,用完将存活的对象复制到另外一块 B,一次性清空 A,然后改分配新对象到 B,如此循环。
缺点:
3、标记-整理算法(Mark-Compact)
标记存活的对象,统一移到内存区域的一边,清空占用内存边界以外的内存。
缺点:
相同点:
区别:
方法体内没有对字符串的并发操作,且存在大量字符串拼接操作,建议使用 StringBuilder,效率较高。
static:
final:
final 和 static 修饰成员变量加载过程例子
import java.util.Random;
public class TestStaticFinal {
public static void main(String[] args) {
StaticFinal sf1 = new StaticFinal();
StaticFinal sf2 = new StaticFinal();
System.out.println(sf1.fValue == sf2.fValue);//打印false
System.out.println(sf1.sValue == sf2.sValue);//打印true
}
}
class StaticFinal {
final int fValue = new Random().nextInt();
static int sValue = new Random().nextInt();
}
INT[(M)] [UNSIGNED] [ZEROFILL] M 默认为11
10 就是上述语句里的 M,指最大显示宽度,最大值为 255
最大显示宽度意思是,如果是 int(10),字段存的值是 10,则,显示会自动在之前补 8 个 0,显示为 0000000010
int 类型在数据库里面存储占 4 个字节的长度
时间复杂度分析:
1、大 O 复杂度表示法:T(n) = O(f(n)),公式中的 O,表示代码的执行时间 T(n) 与 f(n) 表达式成正比
2、最好情况时间复杂度:代码在最理想情况下执行的时间复杂度
3、最坏情况时间复杂度:代码在最坏情况下执行的时间复杂度
4、平均时间复杂度:代码在所有情况下执行的次数的加权平均值
5、均摊时间复杂度:极少数高级别复杂度且发生具有时序关系时,可以将这几个高级别的复杂度均摊到低级别复杂度上,一般均摊结果就等于低级别复杂度
空间复杂度分析:
常见的复杂度:
优点:
缺点:
为文件在另外一个位置建立一个同步的链接
链接分为:
1、软链接
2、硬链接
需要注意:
ln 命令会保持每一处链接文件的同步性,也就是说,不论你改动了哪一处,其它的文件都会发生相同的变化
ln 的链接又分软链接和硬链接两种,软链接就是ln –s 源文件 目标文件,它只会在你选定的位置上生成一个文件的镜像,不会占用磁盘空间;硬链接 ln 源文件 目标文件,没有参数 -s, 在指定的位置上生成一个和源文件大小相同的文件,无论是软链接还是硬链接,文件都保持同步变化
ln 指令用在链接文件或目录,如同时指定两个以上的文件或目录,且目标目录已经,则会把前面指定的所有文件或目录复制到该目录中。若同时指定多个文件或目录,且目标目录不存在,则会出现错误信息
常用参数:
-b 删除,覆盖之前建立的链接
-s 软链接
-v 显示详细处理过程
Collections 工具类的 sort() 方法有两种方式
spring 是一个开源的轻量级 JavaBean 容器框架。使用 JavaBean 代替 EJB ,并提供了丰富的企业应用功能,降低应用开发的复杂性。
不一定,看 Reference 类型,弱引用在 GC 时会被回收,软引用在内存不足的时候,即 OOM 前会被回收,但如果没有在 Reference Chain 中的对象就一定会被回收。
删除一个目录中的一个或多个文件或目录。如果没有使用 -r 选项,则 rm 不会删除目录。如果使用 rm 来删除文件,通常仍可以将该文件恢复原状。
命令语法:rm [选项] 文件…
rm -i *.log 删除任何 .log 文件,删除前逐一询问确认
rm -rf test 强制删除 test 目录或文件,无需确认
答案:C
分析:
Java 类中不写构造方法,编译器会默认提供一个无参构造
方法名可以与类名相同,但不符合命名规范,类名首字母建议大写,方法名建议首字母小写
一个类中可以定义多个构造方法,这就是构造方法的重载
Java 创建对象的方式:
上述 1、2 会调用构造函数
上述 3、4 不会调用构造函数
package constxiong.interview;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 测试创建对象
* @author ConstXiong
* @date 2019-10-31 11:53:31
*/
public class TestNewObject implements Cloneable, Serializable{
private static final long serialVersionUID = 1L;
public TestNewObject() {
System.out.println("Constructor init");
}
public static void main(String[] args) throws Exception {
TestNewObject o1 = new TestNewObject();
TestNewObject o2 = TestNewObject.class.newInstance();
TestNewObject o3 = TestNewObject.class.getConstructor().newInstance();
TestNewObject o4 = (TestNewObject)o1.clone();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o1);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
TestNewObject o5 = (TestNewObject)ois.readObject();
}
}
打印
Constructor init
Constructor init
Constructor init
1、XML 中使用 #{},Java 代码中传入 “%参数值%”
Java:
list users = mapper.select(Collections.singleMap("name", "%constxiong%"));
XML:
2、XML 中使用 ${},Java 代码中传入
Java:
list users = mapper.select(Collections.singleMap("name", "constxiong"));
XML:
存储过程:
函数:
list 的缩写,通过 ls 命令不仅可以查看 linux 文件夹包含的文件,而且可以查看文件权限(包括目录、文件夹、文件权限)、查看目录信息等等。
常用命令:
ls -a 列出目录所有文件,包含以.开始的隐藏文件
ls -A 列出除.及..的其它文件
ls -r 反序排列
ls -t 以文件修改时间排序
ls -S 以文件大小排序
ls -h 以易读大小显示
ls -l 除了文件名之外,还将文件的权限、所有者、文件大小等信息详细列出来
ls -lhrt 按易读方式按时间反序排序,并显示文件详细信息
ls -lrS 按大小反序显示文件详细信息
ls -l t* 列出当前目录中所有以"t"开头的目录的详细内容
ls | sed "s:^:`pwd`/:" 列出文件绝对路径(不包含隐藏文件)
find $pwd -maxdepth 1 | xargs ls -ld 列出文件绝对路径(包含隐藏文件)
截取字符串统计字符串出现次数
通过替换字符串,统计字符串出现次数
通过正则表达式,统计字符串出现次数
package constxiong.interview;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
统计一段长字符串中某字符串的出现次数
@author ConstXiong
@date 2019-11-13 11:01:22
*/
public class TestCountWordTimesInText {
public static void main(String[] args) {
String text = “统计一CX段长CX字符串中某字符串的出C现X次cx数”;
String word = “CX”;
System.out.println(countWordTimesByCutString(text, word));
System.out.println(countWordTimesByReplace(text, word));
System.out.println(countWordTimesByRegex(text, word));//正则匹配,需要注通配符的使用对结果的影响
}
/**
/**
/**
/**
}
对于 try 和 finally 至少一个语句块包含 return 语句的情况:
如下面的例子
public static void main(String[] args) {
System.out.println(getString());
}
public static String getString() {
String str = "A";
try {
str = "B";
return str;
} finally {
System.out.println("finally change return string to C");
str = "C";
}
}
打印
finally change return string to C
B
finally 语句块中新增 return 语句
public static void main(String[] args) {
System.out.println(getString());
}
public static String getString() {
String str = "A";
try {
str = "B";
return str;
} finally {
System.out.println("finally change return string to C");
str = "C";
return str;
}
}
打印结果
finally change return string to C
C
redis.expire(key, expiration)
过期处理策略:
var arr = [1,2,3];
var arr = new Array(1, 2, 3);
var arr = new Array();
arr[0]=1;
arr[1]=2;
arr[2]=3;
Bean 的生命周期按最详细的来说如下(参照小马哥的 Spring 专栏课件),其实细节还远不止如此,都在代码 AbstractBeanFactory#doGetBean 里,可以自己走起!
1、Spring Bean 元信息配置阶段。xml、properties 配置文件中配置 bean 的信息;代码中使用注解标识 bean;代码中使用 api 设置 BeanDefinition 的属性值或构造方法参数。
2、Spring Bean 元信息解析阶段。BeanDefinitionReader 的三种实现类(XmlBeanDefinitionReader、PropertiesBeanDefinitionReader、GroovyBeanDefinitionReader),将配置信息解析为 BeanDefinition;AnnotatedBeanDefinitionReader 将注解标识的类或方法,解析成 BeanDefinition。
3、Spring Bean 注册阶段。将 BeanDefinition 注册到 BeanDefinitionRegistry 中。
4、Spring BeanDefinition 合并阶段。AbstractBeanFactory#getMergedBeanDefinition 方法,将有父子层次关系的 BeanDefinition 合并成 RootBeanDefinition。
5、Spring Bean Class 加载阶段。AbstractBeanFactory#resolveBeanClass 方法,若 BeanDefinition 中的 beanClass 不存在,获取类加载器根据包路径+类名加载其 Class 对象,用于后面的实例化。
6、Spring Bean 实例化前阶段。AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation,执行 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
7、Spring Bean 实例化阶段。AbstractAutowireCapableBeanFactory#instantiateBean,执行 InstantiationStrategy#instantiate 方法实例化 bean
8、Spring Bean 实例化后阶段。AbstractAutowireCapableBeanFactory#populateBean,执行 InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
9、Spring Bean 属性赋值前阶段。AbstractAutowireCapableBeanFactory#populateBean 执行设置属性值,InstantiationAwareBeanPostProcessor#postProcessProperties
10、Spring Bean Aware接口回调阶段。AbstractAutowireCapableBeanFactory#initializeBean -> #invokeAwareMethods 方法中执行
11、Spring Bean 初始化前阶段。AbstractAutowireCapableBeanFactory#initializeBean -> #applyBeanPostProcessorsBeforeInitialization 方法执行 BeanPostProcessor#postProcessBeforeInitialization
12、Spring Bean 初始化阶段。AbstractAutowireCapableBeanFactory#initializeBean -> #invokeInitMethods 方法执行
13、Spring Bean 初始化后阶段。AbstractAutowireCapableBeanFactory#initializeBean -> #applyBeanPostProcessorsAfterInitialization 方法执行
14、Spring Bean 初始化完成阶段。在 ApplicationContext 的生命周期里,AbstractApplicationContext#finishBeanFactoryInitialization -> DefaultListableBeanFactory#preInstantiateSingletons -> SmartInitializingSingleton#afterSingletonsInstantiated
15、Spring Bean 销毁前阶段。AbstractBeanFactory#destroyBean -> DisposableBeanAdapter#destroy -> DestructionAwareBeanPostProcessor#postProcessBeforeDestruction
16、Spring Bean 销毁阶段。AbstractBeanFactory#destroyBean 执行 @PreDestroy 标注方法、实现DisposableBean 接口的destroy() 方法、自定义销毁方法
17、Spring Bean 垃圾收集。BeanFactory 被置空,所缓存的 bean 可被 gc
java.io.ByteArrayOutputStream
都可以
打印:132424
创建对象时构造器的调用顺序
源码入口与上题同。
答案:BCD
分析:
只有 public、abstract和默认的 3 种修饰符能够修饰 interface
1. DOM(Document Object Model)
**优点:**可获取和操作 xml 任意部分的结构和数据
**缺点:**需加载整个 XML 文档,消耗资源大
2. SAX(Simple API for XML)
SAX 解析器基于事件的模型,解析 XML 文档时可以触发一系列事件,解析到期望的节点,可以激活一个回调方法,处理该节点上的数据
优点:
缺点:
3. JDOM(Java-based Document Object Model)
JDOM 自身不包含解析器,使用 SAX2 解析器来解析和验证输入XML文档
包含一些转换器以将 JDOM 表示输出成 SAX2 事件流、DOM 模型、XML 文本文档
**优点:**API 简单,方便开发
**缺点:**灵活性较差;性能较差
4. dom4j(Document Object Model for Java)
优点:
缺点:
总结
造成的原因是工程中存在 jar 包编译时所用的 JDK 版本高于工程 build path 中 JDK 的版本。
这里的 version 52 对应 JDK 版本是 1.8,将项目的 build path 中 JDK 的版本调整为高于或等于 1.8 即可。
输入和输出都是从程序的角度来说的。
输入流:数据流向程序
输出流:数据从程序流出。
字节流:一次读入或读出是8位二进制
字符流:一次读入或读出是16位二进制
JDK 中后缀是 Stream 是字节流;后缀是 Reader,Writer 是字符流
节点流:直接与数据源相连,读入或写出
处理流:与节点流一块使用,在节点流的基础上,再套接一层
最根本的四大类:InputStream(字节输入流),OutputStream(字节输出流),Reader(字符输入流),Writer(字符输出流)
四大类的扩展,按处理单位区分
常用的流
Redisson
优点:
缺点:
Jedis
优点:
缺点:
Lettuce
优点:
缺点:
Serial
特点:
ParNew
特点:
Parallel Scavenge
特点:
Serial Old
特点:
Parallel Old
特点:
CMS
特点:
G1
特点:
Shenandoah
特点:
ZGC
特点:
是查看目录使用空间情况,与 df 命令不同的是 du 命令是对文件和目录磁盘使用的空间的查看
命令格式:du [选项] [文件]
常用参数:
-a 显示目录中所有文件大小
-k 以KB为单位显示文件大小
-m 以MB为单位显示文件大小
-g 以GB为单位显示文件大小
-h 以易读方式显示文件大小
-s 仅显示总计
-c或--total 除了显示个别目录或文件的大小外,同时也显示所有目录或文件的总和
CSRF:Cross Site Request Forgery(跨站点请求伪造)。
CSRF 攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。
避免方法:
参考:
消息被重复消费,就是消费方多次接受到了同一条消息。根本原因就是,第一次消费完之后,消费方给 MQ 确认已消费的反馈,MQ 没有成功接受。比如网络原因、MQ 重启等。
所以 MQ 是无法保证消息不被重复消费的,只能业务系统层面考虑。
不被重复消费的问题,就被转化为消息消费的幂等性的问题。幂等性就是指一次和多次请求的结果一致,多次请求不会产生副作用。
保证消息消费的幂等性可以考虑下面的方式:
jps,显示系统所有虚拟机进程信息的命令行工具
jstat,监视分析虚拟机运行状态的命令行工具
jinfo,查看和调整虚拟机参数的命令行工具
jmap,生成虚拟机堆内存转储快照的命令行工具
jhat,显示和分析虚拟机的转储快照文件的命令行工具
jstack,生成虚拟机的线程快照的命令行工具
jcmd,虚拟机诊断工具,JDK 7 提供
jhsdb,基于服务性代理实现的进程外可视化调试工具,JDK 9 提供
JConsole,基于JMX的可视化监视和管理工具
jvisualvm,图形化虚拟机使用情况的分析工具
Java Mission Control,监控和管理 Java 应用程序的工具
MAT,Memory Analyzer Tool,虚拟机内存分析工具
vjtools,唯品会的包含核心类库与问题分析工具
arthas,阿里开源的 Java 诊断工具
greys,JVM进程执行过程中的异常诊断工具
GCHisto,GC 分析工具
GCViewer,GC 日志文件分析工具
GCeasy,在线版 GC 日志文件分析工具
JProfiler,检查、监控、追踪 Java 性能的工具
BTrace,基于动态字节码修改技术(Hotswap)实现的Java程序追踪与分析工具
下面可以重点体验下:
Redis 支持五种数据类型
2.8 版以前,Redis 通过同步(sync)和指令传播(command propagate)两个操作完成同步
2.8 版开始新增 PSYNC 指令,PSYNC 具有两种模式:
Iterator 接口源码中的方法
JDK 1.8 源码如下:
//是否有下一个元素
boolean hasNext();
//下一个元素
E next();
//从迭代器指向的集合中删除迭代器返回的最后一个元素
default void remove() {
throw new UnsupportedOperationException("remove");
}
//遍历所有元素
default void forEachRemaining(Consumer super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
Iterator 的使用示例
public class TestIterator {
static List list = new ArrayList();
static {
list.add("111");
list.add("222");
list.add("333");
}
public static void main(String[] args) {
testIteratorNext();
System.out.println();
testForEachRemaining();
System.out.println();
testIteratorRemove();
}
//使用 hasNext 和 next遍历
public static void testIteratorNext() {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
System.out.println(str);
}
}
//使用 Iterator 删除元素
public static void testIteratorRemove() {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if ("222".equals(str)) {
iterator.remove();
}
}
System.out.println(list);
}
//使用 forEachRemaining 遍历
public static void testForEachRemaining() {
final Iterator iterator = list.iterator();
iterator.forEachRemaining(new Consumer() {
public void accept(String t) {
System.out.println(t);
}
});
}
}
注意事项
在迭代过程中调用集合的 remove(Object o) 可能会报 java.util.ConcurrentModificationException 异常
forEachRemaining 方法中 调用Iterator 的 remove 方法会报 java.lang.IllegalStateException 异常
//使用迭代器遍历元素过程中,调用集合的 remove(Object obj) 方法可能会报 java.util.ConcurrentModificationException 异常
public static void testListRevome() {
ArrayList aList = new ArrayList();
aList.add(“111”);
aList.add(“333”);
aList.add(“222”);
System.out.println(“移除前:”+aList);
Iterator iterator = aList.iterator();
while(iterator.hasNext()) {
if("222".equals(iterator.next())) {
aList.remove("222");
}
}
System.out.println("移除后:"+aList);
}
//JDK 1.8 Iterator forEachRemaining 方法中 调用Iterator 的 remove 方法会报 java.lang.IllegalStateException 异常
public static void testForEachRemainingIteRemove () {
final Iterator iterator = list.iterator();
iterator.forEachRemaining(new Consumer() {
public void accept(String t) {
if ("222".equals(t)) {
iterator.remove();
}
}
});
}
表示右移,如果该数为正,则高位补0,若为负数,则高位补1
表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0
测试代码:
System.out.println("16 <<1 : " + (16 <<1));
System.out.println("16 >> 3 : " + (16 >> 3));
System.out.println("16 >> 10 : " + (16 >> 10));
System.out.println("1 >> 1 : " + (1 >> 1));
System.out.println("16 >>> 2 : " + (16 >>> 2));
System.out.println("-16 >> 2 : " + (-16 >> 2));
System.out.println("-16 <<2 : " + (-16 <<2));
System.out.println("-16 >>> 2 : " + (-16 >>> 2));
打印结果:
16 <<1 : 32
16 >> 3 : 2
16 >> 10 : 0
1 >> 1 : 0
16 >>> 2 : 4
-16 >> 2 : -4
-16 <<2 : -64
-16 >>> 2 : 1073741820
简单理解:
1 相当于除以2
不考虑高位的正负号,正数的 >>> 等同于 >>
PS:位移操作涉及二进制、原码、补码、反码相关,可参考:
索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息
使用索引目的是加快检索表中数据
使用场景:
在 Mapper xml 中的标签里使用可以完成 1对1 关联查询
在 Mapper xml 中的标签里使用可以完成 1对多 关联查询
//sql
create table user (
id int primary key,
name varchar(400)
);
insert user info VALUES(1, ‘ConstXiong1’);
create table info (
user_id int primary key,
name varchar(400)
);
insert into info VALUES(1, ‘大熊’);
create table article (
user_id int,
title varchar(400)
);
insert into article VALUES(1, ‘文章1’);
insert into article VALUES(1, ‘文章2’);
//Mapper xml
select user.id, user.name, info.user_id, info.name as info_name from user,info where user.id = info.user_id select user.id, user.name, article.user_id, article.title from user,article where user.id = article.user_id//User.java
/**
用户表模型
*/
public class User {
private Integer id;
private String name;
private String mc;
private Info info;
private List
public User() {
}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMc() {
return mc;
}
public void setMc(String mc) {
this.mc = mc;
}
public Info getInfo() {
return info;
}
public void setInfo(Info info) {
this.info = info;
}
public List
public void setArticles(List
@Override
public String toString() {
return “User{” +
“id=” + id +
“, name='” + name + ‘’’ +
“, mc='” + mc + ‘’’ +
“, info=” + info +
“, articles=” + articles +
‘}’;
}
}
//测试代码
System.out.println(“------ selectUserWithInfo ------”);
user = userMapper.selectUserWithInfo();
System.out.println(user);
System.out.println(“------ selectUserWithArticles ------”);
user = userMapper.selectUserWithArticles();
System.out.println(user);
//打印
------ selectUserWithInfo ------
User{id=1, name=‘ConstXiong1’, mc=‘null’, info=Info{userId=1, name=大熊}, articles=null}
------ selectUserWithArticles ------
User{id=1, name=‘ConstXiong1’, mc=‘null’, info=null, articles=[Article{userId=1, title=‘文章1’}, Article{userId=1, title=‘文章2’}]}
完整 Demo:
https://javanav.com/val/a9fe4555c1614b40b0da07afeabf2a66.html
反射使用的不当,对性能影响比较大,一般项目中直接使用较少。
反射主要用于底层的框架中,Spring 中就大量使用了反射,比如:
MySQL 中存在 NULL 值的列也是走索引的
计划对列进行索引,应尽量避免把它设置为可空,因为这会让 MySQL 难以优化引用了可空列的查询,同时增加了引擎的复杂度
Dubbo 是阿里巴巴公司开源的一个基于Java的高性能开源 RPC 框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成。
Dubbo 后来没有维护,当当网基于 Dubbo 做了一些扩展,推出 Dubbox:
为了减少对象引用的扫描,使用 OopMap 的数据结构在特定的位置记录下栈里和寄存器里哪些位置是引用;
但为了避免给每条指令都生成 OopMap 记录占用大量内存的问题,只在特定位置记录这些信息。
安全点的选定既不能太少以至于让收集器等待时间过长,也不能太过频繁以至于过分增大运行时的内存负荷。安全点位置的选取基本上是以“是否具有让程序长时间执行的特征”为标准进行选定的,如方法调用、循环跳转、异常跳转等都属于指令序列复用。
Java 中有 8 个基本类型,分别对应的包装类如下
为什么要有包装类
基本数据类型和包装类之间的转换
-XX:+PrintCommandLineFlags 参数可以打印出所选垃圾收集器和堆空间大小等设置
如果开启了 GC 日志详细信息,里面也会包含各代使用的垃圾收集器的简称
Redis 的 list 结构可以作为队列使用,rpush 生产消息,lpop 消费消息,lpop 没有取到消息时,可以让线程休眠一会再获取消息
blpop 指令,在队列没有消息时,会阻塞线程直到消息被生产,获取消息
List、Set 的父接口是 Collection
Map 不是其子接口,与 Collection 接口相互独立
思路:
引起 SQL 查询很慢的原因与解决办法:
1、没有索引。解决办法:
2、索引未生效。解决办法:
3、单表数据量太大。解决办法:
提升性能的一些技巧:
字段最多存放 50 个字符
如 varchar(50) 和 varchar(200) 存储 “ConstXiong” 字符串所占空间是一样的,后者在排序时会消耗更多内存
Spring 中定义了资源接口:
内建了几种实现:
使用层面
Map 的实现类有 HashMap、LinkedHashMap、TreeMap
HashMap是有无序的
LinkedHashMap 和 TreeMap 是有序的。LinkedHashMap 记录了添加数据的顺序;TreeMap 默认是升序
LinkedHashMap 底层存储结构是哈希表+链表,链表记录了添加数据的顺序
TreeMap 底层存储结构是二叉树,二叉树的中序遍历保证了数据的有序性
备份就是把数据库复制到转储设备的过程
从物理与逻辑的角度:
从数据库的备份角度: