0%

文章字数:111,阅读全文大约需要1分钟
1.自定义类加载器, 实现findClass方法。loadClass在找不到类时会调用此方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MyClassLoader extends ClassLoader {

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try{
String filePath = "/Users/zhanjun/Desktop/" + name.replace('.', File.separatorChar) + ".class";
//指定读取磁盘上的某个文件夹下的.class文件:
File file = new File(filePath);
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
//调用defineClass方法,将字节数组转换成Class对象
Class<?> clazz = this.defineClass(name, bytes, 0, bytes.length);
fis.close();
return clazz;
}catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {

}
return super.findClass(name);
}
}
  1. 调用
1
2
3
public static void main(String[] args) throws Exception {
Class clazz0 = new MyClassLoader().loadClass("com.sankuai.discover.memory.OOM");
}

文章字数:658,阅读全文大约需要2分钟

自旋锁指的是线程在为获取到许可的情况下循环获取许可状态

实现

  1. TAS(Test And Set Lock)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TASLock implements Lock {
//初始值为false;
private AtomicBoolean mutex=new AtomicBoolean(false);


@Override
public void lock() {
//返回之前的值,并设置为true fixme 如果之前未true则进入自旋状态
//fixme mutex之前状态时FALSE时才返回,表示获取到锁
//原子变量的改动对所有线程都可见
while(mutex.getAndSet(true)){}
}

@Override
public void unlock() {
mutex.set(false);//fixme ?释放锁?
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TASLockMain {
private static TASLock cost=new TASLock ();

public static void func(){
//自旋获取许可
cost.lock();
//释放许可
cost.unlock();
}

public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
Thread t=new Thread(()-> func());
t.start();
}

}
}

不停的设置值会造成不停通知其他芯片值更改,产生缓存一致性风暴

  1. TTASLock(Test Test And Set Lock)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

package com.test.lock;

import java.util.concurrent.atomic.AtomicBoolean;

/**
* 测试-测试-设置自旋锁,使用AtomicBoolean原子变量保存状态
* 分为两步来获取锁
* 1. 先采用读变量自旋的方式尝试获取锁
* 2. 当有可能获取锁时,再使用getAndSet原子操作来尝试获取锁
* 优点是第一步使用读变量的方式来获取锁,在处理器内部高速缓存操作,不会产生缓存一致性流量
* 缺点是当锁争用激烈的时候,第一步一直获取不到锁,getAndSet底层使用CAS来实现,一直在修改共享变量的值,会引发缓存一致性流量风暴
* **/
public class TTASLock implements Lock{

private AtomicBoolean mutex = new AtomicBoolean(false);

@Override
public void lock() {
while(true){
// 第一步使用读操作,尝试获取锁,当mutex为false时退出循环,表示可以获取锁
while(mutex.get()){}
// 第二部使用getAndSet方法来尝试获取锁
if(!mutex.getAndSet(true)){
return;
}

}
}

@Override
public void unlock() {
mutex.set(false);
}

public String toString(){
return "TTASLock";
}
}

先查看是否可用再设置,少了cas次数。但是在高征用的情况下会导致多次操作才能获取到锁,增加cas次数

  1. 回退算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

package com.test.lock;

import java.util.Random;

/**
* 回退算法,降低锁争用的几率
* **/
public class Backoff {
private final int minDelay, maxDelay;

private int limit;

final Random random;

public Backoff(int min, int max){
this.minDelay = min;
this.maxDelay = max;
limit = minDelay;
random = new Random();
}

// 回退,线程等待一段时间
public void backoff() throws InterruptedException{
int delay = random.nextInt(limit);
limit = Math.min(maxDelay, 2 * limit);
Thread.sleep(delay);
}
}

package com.test.lock;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 回退自旋锁,在测试-测试-设置自旋锁的基础上增加了线程回退,降低锁的争用
 * 优点是在锁高争用的情况下减少了锁的争用,提高了执行的性能
 * 缺点是回退的时间难以控制,需要不断测试才能找到合适的值,而且依赖底层硬件的性能,扩展性差
 * **/
public class BackoffLock implements Lock{

    private final int MIN_DELAY, MAX_DELAY;
    
    public BackoffLock(int min, int max){
        MIN_DELAY = min;
        MAX_DELAY = max;
    }
    
    private AtomicBoolean mutex = new AtomicBoolean(false);
    
    @Override
    public void lock() {
        // 增加回退对象
        Backoff backoff = new Backoff(MIN_DELAY, MAX_DELAY);
        while(true){
            // 第一步使用读操作,尝试获取锁,当mutex为false时退出循环,表示可以获取锁
            while(mutex.get()){}
            // 第二部使用getAndSet方法来尝试获取锁
            if(!mutex.getAndSet(true)){
                return;
            }else{
                //回退
                try {
                    backoff.backoff();
                } catch (InterruptedException e) {
                }
            }    
            
        }
    }

    @Override
    public void unlock() {
        mutex.set(false);
    }

    public String toString(){
        return "TTASLock";
    }
}

获取失败后线程休眠一段时间,减少冲突概率。缺点是休眠时间不好设置,需要根据硬件条件调整参数。


文章字数:201,阅读全文大约需要1分钟

java获取当前时间可以使用 System.currentTimeMillis()获取当前毫秒数,也可以直接new Date()获取当前日期类,date.getTime()获取毫秒数

联系

Date的无参构造方法

1
2
3
public Date() {
this(System.currentTimeMillis());
}

本质上也是调用System.currentTimeMillis(),所以如果需要获取当前毫秒数,System.currentTimeMillis()比起new Date().getTime()少了引用及转换,效率更高。

精确度

System.currentTimeMillis()依赖系统底层实现,精确度无法保证。毫秒级别的时间,测量从1970年1月1日0到现在的毫秒数。
System.nanoTime也是依赖系统底层实现,但是精度为纳秒。此方法依赖cpu自身的计时器,所以不同机器返回的时间不同,只能用作计算某一时间内的时间差。

纳秒转换成毫秒

1
System.nanoTime()/1000000L

文章字数:165,阅读全文大约需要1分钟

起因是邮件修改密码偶尔会提示token过期,后台断点发现应该是一次提交的表单提交了两次。

可能的原因

  1. 提交的按钮放在form表单里(自带提交属性),但是点击事件里又提交了一次。(里面用的是异步提交)

  2. 回车触发提交事件,可能事件被多次触发(加了日志输出,排除了)

  3. 其它代码也进行了提交操作

解决

最后在chromeevent listeners找到了另一个表单提交事件,是框架封装的提交,和自己的提交重复了。


文章字数:416,阅读全文大约需要1分钟

获取服务器ip,之前一直用request.getHeader("Host")获取。后来发现本机访问时获取到的是127.0.0.1这个地址,而我需要的是其它地方也能访问到的地址。

方法

  1. 解析HostName获取,直接InetAddress.getLocalHost().getHostAddress()

  2. 遍历网卡的地址(即本机所有ip地址),从中找到符合规则的。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.*;
import java.util.function.Function;

import org.apache.commons.lang3.StringUtils;

/**
* @author colin.cheng
* @date
*/
public class test {
/**
* 两种方法,单网卡直接获取,多网卡选择获取
*
* @param args
* @throws Exception
*/
public static void main(String[] args) {
// 1. 单网卡时,通过解析本机hostName获取ip地址
try {
InetAddress inet = InetAddress.getLocalHost();
String ipAddress = inet.getHostAddress();
System.out.println("单网卡获取ip地址 = " + ipAddress);
} catch (UnknownHostException e) {
e.printStackTrace();
}

// 2. 多网卡状态下从所有的网卡ip中找出需要的ip
// 这里的逻辑是能找到外网地址返回外网,不能找到返回最后一个内网地址
try {
String serverIp = getServerIpv4Address(ip -> {
// 本机地址过滤
if (StringUtils.equals("127.0.0.1", ip.trim())) {
return 0;
}
// 内网地址权重1
if (ip.matches(
"^(127\\.0\\.0\\.1)|(localhost)|(10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})|(172\\.((1[6-9])|(2\\d)|(3[01]))\\.\\d{1,3}\\.\\d{1,3})|(192\\.168\\.\\d{1,3}\\.\\d{1,3})$")) {
return 1;
}
// 外网地址权重2
return 2;
});

System.out.println("遍历网卡地址选取ip = " + serverIp);
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 通过网卡查找网卡绑定的ipv4地址,并交由filter进行权重判断
*
* @param filter
* 自定义选择逻辑
* @return 返回权重最大的ip
* @throws Exception
*/
private static String getServerIpv4Address(Function<String, Integer> filter) throws Exception {
Map<Integer, String> resMap = new HashMap<>();
// 获得本机的所有网络接口
Enumeration<NetworkInterface> nifs = NetworkInterface.getNetworkInterfaces();
while (nifs.hasMoreElements()) {
NetworkInterface nif = nifs.nextElement();
// 获得与该网络接口绑定的 IP 地址,一般只有一个
Enumeration<InetAddress> addresses = nif.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
// 只关心 IPv4 地址
if (addr instanceof Inet4Address) {
String ip = addr.getHostAddress();
// 自定义过滤器,返回权重。
Integer index = filter.apply(ip);
if (index != 0) {
resMap.put(index, ip);
}
}
}
}
Collection<Integer> collection = resMap.keySet();
if (collection.size() > 0) {
Integer i = Collections.max(collection);
return resMap.get(i);
}
// 没有匹配结果
return null;
}
}

文章字数:184,阅读全文大约需要1分钟

注解创建过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.xc.common.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

/**
* @ClassName: FilterDemo01
* @Description:filter的三种典型应用: <br/>
* 1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法, 即是否让目标资源执行<br/>
* 2、在让目标资源执行之前,可以对request\response作预处理,再让目标资源执行 <br/>
* 3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能 <br/>
*/
@WebFilter(filterName = "FilterDemo01", urlPatterns = { "/*" })
public class FilterDemo01 implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("----FilterDemo01过滤器初始化----");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

// 对request和response进行一些预处理
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");

System.out.println("FilterDemo01执行前!!!");
chain.doFilter(request, response); // 让目标资源执行,放行
System.out.println("FilterDemo01执行后!!!");
}

@Override
public void destroy() {
System.out.println("----过滤器销毁----");
}
}

文章字数:192,阅读全文大约需要1分钟

遇到一个无法解决的bug?debug发现代码迷之跳跃?添加了代码重新编译,然而出现迷之错误?请收看大型通关策略类游戏《java从入门到放弃》攻略之—《踩坑终极指南》

第一步清空数据库数据

  • 设置jpa为运行时创建(如果有覆盖)
1
spring.jpa.properties.hibernate.hbm2ddl.auto=create

第二步清空redis

1
2
Redis0:> select 01 //选择数据库
Redis01:> flushAll //清空缓存

第三步重启软件

到这一步之后重启软件一般因为缓存(spring,redis…)导致的问题都能解决了

第四步重启电脑

此问题一般是端口占用,服务冲突,软件抽了等。。。

第五步换电脑

怎么可能是我的代码问题?


文章字数:187,阅读全文大约需要1分钟

通过注入一个List的方法注入多个实现类,然后再循环遍历列表,得出需要使用的实现类。可以作为策略模式的实现之一。

@Autowire 和 @Resource

  1. 这两个都是用来装配bean的,可以加在字段或方法上(get,set,构造方法)
  2. Autowired默认使用类型装配,找到多个才会根据变量名找
  3. Autowired默认requiredtrue即找不到会报错
  4. Resource是先通过名字找,然后才是类型

使用@Autowired实现

Autowired是通过类型先匹配的,所以可以匹配到多个类。如果接受的变量刚好是集合,便能把匹配的所有类都赋值给集合。

1
private List<BaseService> services;

文章字数:204,阅读全文大约需要1分钟

逻辑关联/逻辑外键 和 物理关联/物理外键 指的是 实体对象/数据库 中 对象和对象/表与表 的关系是否有设计上的关联

逻辑关联

逻辑关联指的是实体类上一个值对应另一个对象的id,这个对应仅限于思想上的。
逻辑外键就是思想上数据库中的一个字段对应另一个表的id,但是没有任何额外操作。写程序时自己判断如何关联/连表查询

物理关联

物理关联指的是实体类上明确标注了对象间的管理,管理维护是由系统来维护而不是额外代码手动维护。
物理外键即设置了外键关联。


文章字数:63,阅读全文大约需要1分钟

选择排序就是选出最大的,放在第一个,再选出最大的,放第二个。。。

时间复杂度 o(n^2)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void sort(int arr[]){
for( int i = 0;i < arr.length ; i++ ){
int min = i;//最小元素的下标
for(int j = i + 1;j < arr.length ; j++ ){
if(arr[j] < arr[min]){
min = j;//找最小值
}
}
//交换位置
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}