0%

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

使用copyProperties()复制时Boolean类型的一直无法复制,最后看到了一些信息

  1. 在网上找到分析源码513行只支持boolean
    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (argCount == 0) {
    if (name.startsWith(GET_PREFIX)) {
    // Simple getter
    pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null);
    } else if (resultType == boolean.class && name.startsWith(IS_PREFIX)) {
    // Boolean getter
    pd = new PropertyDescriptor(this.beanClass, name.substring(2), method, null);
    }
    }
    Boolea类型的getXxx()可以使用,如果方法是isXxx()就只有boolean可以
    属性名不要用isXXX命名了

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

CAP原则是只一个分布式系统中,一致性Consistency、可用性Availability、分区容错性Partition tolerance,三个要素只能同时实现两点,不可能三者兼顾。

具体解释

  1. 一致性c: 在分布式系统中,所有的数据备份,在同一时刻是否相同的值。(节点一收到变更数据的请求之后马上通知其他节点,保证数据的统一。)
  2. 可用性A: 在集群中一部分节点故障后,集群整体是否还能相应客户端的读写请求。(只要有数据请求,就必须给回应。即使部分节点故障)
  3. 分区容错性P: 大多数分布式系统分布在多个子网中,每个子网就是一个区。区之间通信可能失败,应对这种情况的设计就是保证分区容错性。(p一般是无法避免的,所以剩下的ca无法同时做到)

ConsistencyAvailability的矛盾

因为通信失败的情况常有可能发生,所以分区容错性很重要。根据CAP原则,ConsistencyAvailability就只能选择其一了。
保证数据一致性则需要数据完全同步之后再开放读写,如果此时通信有问有,就无法保证可用性了,因为有部分节点必然被锁定。或者通信有问题,节点没有获取到最新的数据变更请求,从而达不到数据一致性。


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

BufferIO流是为了效率而创造出来的,然而实际上不是所有的情况下效率都比普通io高。

源码分析

  1. BufferedInputStream
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
public synchronized int read(byte b[], int off, int len)
throws IOException
{
getBufIfOpen(); // Check for closed stream
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}

int n = 0;
for (;;) {
int nread = read1(b, off + n, len - n);
if (nread <= 0)
return (n == 0) ? nread : n;
n += nread;
if (n >= len)
return n;
// if not closed but no bytes available, return
InputStream input = in;
if (input != null && input.available() <= 0)
return n;
}
}


private int read1(byte[] b, int off, int len) throws IOException {
int avail = count - pos;
if (avail <= 0) {
/* If the requested length is at least as large as the buffer, and
if there is no mark/reset activity, do not bother to copy the
bytes into the local buffer. In this way buffered streams will
cascade harmlessly. */
if (len >= getBufIfOpen().length && markpos < 0) {
return getInIfOpen().read(b, off, len);
}
fill();
avail = count - pos;
if (avail <= 0) return -1;
}
int cnt = (avail < len) ? avail : len;
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
pos += cnt;
return cnt;
}

BufferedInputStreamread方法本质上也是调用FilterInputStreamread方法,IO的次数差不多,特殊情况下还会比FilterInputStream多。

  1. BufferedOutputStream
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
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}

public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}

public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}

初始化的时候声明了一个8192大小的byte[]数组,每次写入都是直接写入数组中。当调用刷新方法,或者数组满了才会调用FilterOutputStreamwrite将数组中的数据写入IO

分析

  1. read方法在获取到的长度小于预期的长度时会再次尝试读取剩余的长度,其它和原生IO一样

  2. write方法本质是先把数据写入内置的byte[]中,等到了一定大小再调用父类方法一次性写入。和自己在外部把信息写入byte[]再写入是一样的。

  3. write方法在写入的数据大于初始化缓冲区的情况下还会增加IO次数,因为写入后检测到大于缓存区就会马上写入IO,再写入剩下的。


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

  • 创建一个类
1
2
3
4
5
public class UserServiceImpl {
public void add() {
System.out.println("添加了一个用户");
}
}
  • 代理类
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
public class UserServiceCGlib implements MethodInterceptor {
private Object target;

public UserServiceCGlib(Object target) {
this.target = target;
}

public Object getProxyInstance() {
// 工具类
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(target.getClass());
// 设置回调函数
enhancer.setCallback(this);
// 创建代理
return enhancer.create();
}

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
Object result = methodProxy.invokerSuper(o, object);
System.out.println("after");
return result;
}
}
  • 使用
1
2
3
UserServiceCGlib serviceCGlib = new UserServiceCGlib(new UserServiceImpl());
UserServiceImpl userService = (UserServiceImpl)serviceCGlib.getProxyIntance();
userService.addUser();

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

CI

  1. 持续集成CONTINUOUS INTEGRATION
    持续集成的环境中,开发人员会频繁提交代码到主干。持续集成就是对于这些代码的自动化测试,对于可能出现的问题进行预警

  2. 优点

  • 每个新功能可以创建自动化测试用例
  • 可以监控代码提交情况,对每个新提交都进行测试

CD

  1. 持续交付CONTINUOUS DELIVERY
    自动化发布流,定时发布,以及一键发布。最好小批量发布,以便出现问题后可以排查

  2. 优点
    节约时间和人力成本

CD

  • 持续部署CONTINUOUS DEPLOYMENT
    代码提交到分支之后自动被构建、测试、部署到生产环境

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

CAS(Compare-and-Swap),比较并替换。java并发包中很多类都采用了CAS技术,当获取到的内存地址和期待的内存地址相等时才将值修改,否则将或获取到的新地址变成期望地址,并循环进行下一次判断,直到成功赋值。保证代码的原子性。

使用例子

1
2
3
4
private static AtomicInteger race = new AtomicInteger(0);
public static void increase(){
race.getAndIncrement();//原子操作,race++
}

CAS缺点

  • 循环时间开销可能会很大
  • 只能保证一个共享变量进行原子操作
  • ABA问题:一个变量在读取时是A,准备赋值时检测也是A。但是可能是从A变成B,又从B变成了A,发生过变化。
    带有标记的引用类AtomicStampedReference可以通过控制变量的版本来保证CAS的正确

AtomicStampedReference

  1. 循环1-1000
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class test1 {
    private static AtomicStampedReference<Integer> integer = new AtomicStampedReference<>(0,0);
    public static void main(String[] args) {
    for(int i=0;i<1000;i++){
    final int timeStamp = integer.getStamp();
    new Thread(){
    @Override
    public void run() {
    System.out.println("timeStamp = " + timeStamp);
    while (true){
    if(integer.compareAndSet(integer.getReference(),integer.getReference()+1,timeStamp,timeStamp+1)){
    break;
    }
    }
    }
    }.run();
    }
    }
    }
  • integer.getStamp();获取版本号
  • compareAndSet(预期对象,修改后,预期版本,修改后版本)返回成功或失败,失败应该手动进入循环直到完成替换。

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

步骤

  1. 查看CPU占用率高的程序
1
> top

查看CPU占用最高的几个程序

  1. 查看PID对应的程序具体信息
1
ps -ef | grep PID
  1. 输出具体日志
1
jstack -l PID >> PID.log
  1. 查看程序内部哪个线程产生的问题
1
top -Hp PID
  1. 查看改线程的代号
1
printf "%x\n" HID
  1. 通过代号在日志中查找问题详细信息(输出20行 -A20)
1
grep id -A20 PID.log

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

日历类Calender获取给定时间上个月的日期,总共有两种方法获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
// 获取默认日历
Calendar c1 = Calendar.getInstance();
Date lastMonth = c1.getTime();
System.out.println("原时间:" + format.format(lastMonth));

// 方法1 add 直接在原时间上加减
c1.add(Calendar.MONTH, -1);
lastMonth = c1.getTime();
System.out.println("add之后时间:" + format.format(lastMonth));

// 重置时间为当前
c1.setTime(new Date());

// 方法2 set 设置新时间, 负数会在高位上相应调整
c1.set(Calendar.MONTH, c1.get(Calendar.MONTH)-1);
lastMonth = c1.getTime();
System.out.println("set之后时间:" + format.format(lastMonth));
}

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

sqlServer数据库环境下出现CannotAcquireLockExceptionLockAcquisitionException异常,记录排查过程

排查过程

  1. 期初以为是接口执行时间过长,导致其它事务获取不到锁导致的。之后模拟锁表,结果发现锁了好几分钟的情况下其他事务都会自旋等待,直到获取到锁才会继续执行。
    结果就是手动加锁只会让接口执行时间边长,并不会抛出上述异常。
  2. 搜索了一下上述异常,发现死锁才会抛出这个异常。之前的尝试最多是等待超时,死锁需要两个事物彼此等待。
  3. 还原发生异常的现场数据,并且运行了一下。发现下发命令的接口执行一分钟左右就会抛出异常。
  4. 使用客户端连接数据库,在程序运行时手动运行以下sql
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    SELECT  
    der.[session_id],der.[blocking_session_id],
    sp.lastwaittype,sp.hostname,sp.program_name,sp.loginame,
    der.[start_time] AS '开始时间',
    der.[status] AS '状态',
    dest.[text] AS 'sql语句',
    DB_NAME(der.[database_id]) AS '数据库名',
    der.[wait_type] AS '等待资源类型',
    der.[wait_time] AS '等待时间',
    der.[wait_resource] AS '等待的资源',
    der.[logical_reads] AS '逻辑读次数'
    FROM sys.[dm_exec_requests] AS der
    INNER JOIN master.dbo.sysprocesses AS sp ON der.session_id=sp.spid
    CROSS APPLY sys.[dm_exec_sql_text](der.[sql_handle]) AS dest
    --WHERE [session_id]>50 AND session_id<>@@SPID
    ORDER BY der.[session_id]
    GO
  5. 最后在抛出异常前发现两个互锁的事务
session_id blocking_session_id
1 2
2 1
  1. 通过查询出来的session_idblocking_session_id可以看出两个事物形成了死锁
  2. 再通过查询出的sql语句定位到具体代码(具体操作要看业务逻辑)
  3. 通过报错下的调用栈也可以定位到其中一条sql语句的位置。

分析

两张表同时被两个事物先后访问,并分别加锁。导致锁表


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

VectorArrayList在迭代的时候如果同时对其进行修改时抛出的异常
参考

原因

迭代器的next()方法在返回下一个时会判断预期执行次数和实际次数

1
2
3
4
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

增加、删除和修改都会导致modCount+1

解决

  1. 单线程下可以使用迭代器Iterator对象下的addremove等方法,这些方法在调用list的响应方法后还会更新expectedModCount
  2. 多线程下可以使用CopyOnWriteArrayList,或者迭代的代码加锁变成同步代码块。
  3. 因为迭代的时候每个线程都是单独的迭代器,所以换成Vector也没用