Kamafeel

求其上,得其中;求其中,得其下,求其下,必败


  • 首页

  • 归档

并发容器

发表于 2020-09-15 | 更新于 2020-09-16 | 分类于 JAVA , 并发
类型 并发特性 其他 网页
ConcurrentHashMap CAS synchronzied volatile 保证内存可见性
CopyOnWriteArrayList CopyOnWrite (COW)ReentrantLock控制写独占锁,volatile修饰唯一数据结构数组,依据happen before写对读可见 内存写时,有2个对象,避免GC 存在延迟 https://juejin.im/post/6844903602436374541
ConcurrentLinkedQueue CAS volatile 链表作为其数据结构 tail 和 head 是延迟更新
ThreadLocal 空间换时间 threadLocal 有可能存在内存泄漏 remove 和锁一样(加锁,释放锁),使用后必须remove
BlockingQueue ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue 阻塞
ArrayBlockingQueue 与 LinkedBlockingQueue condition ReentrantLock LinkedBlockingQueue插入和删除采用2个condition,ArrayBlockingQueue只使用一个

ConcurrentLinkedQueue head指针和 tail指针
alt

ThreadLocal 内存泄露

alt
threadLocal外部强引用被置为null 会被GC,但是entry就存在key为null,这样对应的value无法被GC,导致内存泄露。

SimpleDateFormat里面的Calendar操作高并发会出现SET,Clear的问题,Servlet, @Controller, @Service成员变量同样也存在(可以使用@Scope(value = “prototype”)解决)

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
public class ThreadLocalDemo {
private static ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<>();
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i &lt; 100; i++) {
executorService.submit(new DateUtil("2019-11-25 09:00:" + i % 60));
}
}

static class DateUtil implements Runnable {
private String date;

public DateUtil(String date) {
this.date = date;
}

@Override
public void run() {
if (sdf.get() == null) {
sdf.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
} else {
try {
Date date = sdf.get().parse(this.date);
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
}

AQS.ConditionObject

发表于 2020-09-15 | 分类于 JAVA , 并发

ConditionObject 数据结构
alt

signal

调用condition的signal或者signalAll方法可以将等待队列中等待时间最长的节点移动到同步队列中

await

当调用condition.await()方法后会使得当前获取lock的线程进入到等待队列,如果该线程能够从await()方法返回的话一定是该线程获取了与condition相关联的lock。

alt

线程awaitThread先通过lock.lock()方法获取锁成功后调用了condition.await(释放锁)方法进入等待队列,而另一个线程signalThread通过lock.lock()方法获取锁成功后调用了condition.signal或者signalAll方法,使得线程awaitThread能够有机会移入到同步队列中,当其他线程释放lock后使得线程awaitThread能够有机会获取lock,从而使得线程awaitThread能够从await方法中退出执行后续操作。如果awaitThread获取lock失败会直接进入到同步队列

  • https://blog.csdn.net/SEU_Calvin/article/details/70211712
  • https://juejin.im/post/6844903602419400718

AbstractQueuedSynchronizer(AQS)

发表于 2020-09-15 | 更新于 2020-09-16 | 分类于 JAVA , 并发

AbstractQueuedSynchronizer,简称AQS。AQS是一个用来构建锁和同步器的框架。

数据结构

核心成员变量

1
private volatile int state;//共享变量,使用volatile修饰保证线程可见性

核心代码:

1
2
3
4
5
6
7
public final void acquire(int arg) {
//先看同步状态是否获取成功,如果成功则方法结束返回
//若失败则先调用addWaiter()方法再调用acquireQueued()方法
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

alt

alt
双端双向链表

AQS维护一个共享资源state,通过内置的FIFO来完成获取资源线程的排队工作。(这个内置的同步队列称为”CLH”队列)。该队列由一个一个的Node结点组成,每个Node结点维护一个prev引用和next引用,分别指向自己的前驱和后继结点。AQS维护两个指针,分别指向队列头部head和尾部tail。

当线程获取资源失败(比如tryAcquire时试图设置state状态失败),会被构造成一个结点加入CLH队列中,同时当前线程会被阻塞在队列中(通过LockSupport.park实现,其实是等待态)。当持有同步状态的线程释放同步状态时,会唤醒后继结点,然后此结点线程继续加入到对同步状态的争夺中

  1. 独占式
    tryAcquire tryRelease
  2. 共享式
    tryAcquireShared tryReleaseShared

ReentrantLock(独占式,默认非公平锁效率更高,减少线程上下文切换;使用Condition可以更进一步细粒度控制挂起和唤醒),Semaphore,其他的诸如ReentrantReadWriteLock(写 独占式;读 共享式),SynchronousQueue,FutureTask等等皆是基于AQS。

LockSupport;Unsafe 实现

  • https://www.cnblogs.com/chengxiao/p/7141160.html
  • https://juejin.im/post/6844903601538596877

线程阻塞方式

发表于 2020-09-15 | 更新于 2020-09-16 | 分类于 JAVA , 并发
类型 释放锁 面对对象 必须要做 特性
Thread.sleep 否 仅当前Thread 必须指定休眠时间;处理InterruptedException
Object.wait 是 针对对象 必须先获得对象锁 synchronized (waitObject){waitObject.wait(); }
LockSupport.park 否 针对Thread 会相应InterruptedException,不需要刻意处理 LockSupport.park(Object blocker) jstack命令监控
Condition.await 是 针对Thread /

虽然LockSuport可以指定monitor的object对象,但和object.wait(),两者的阻塞队列并不交叉。object.notifyAll()不能唤醒LockSupport的阻塞Thread

题外话

kill有-9和-15两种参数,默认是-15。如果是-15参数,系统就发送一个关闭信号给进程,然后等待进程关闭。

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
final Thread waitThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread begin");

//等待获取许可
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出thread over.true
System.out.println("thread over." + Thread.currentThread().isInterrupted());

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitThread.start();
//绑定钩子
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
try {
waitThread.interrupt();
waitThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("shutdown success");
}
}));

线程中断

首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。而 Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。具体来说,当对一个线程,调用 interrupt() 时,① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就可以这样做。

-① 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
-② 在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)

Thread thread = new Thread(() -> {
while (!Thread.interrupted()) {
    // do more work.
}
});
thread.start();

// 一段时间以后
thread.interrupt();

具体到你的问题,Thread.interrupted()清除标志位是为了下次继续检测标志位。如果一个线程被设置中断标志后,选择结束线程那么自然不存在下次的问题,而如果一个线程被设置中断标识后,进行了一些处理后选择继续进行任务,而且这个任务也是需要被中断的,那么当然需要清除标志位了。

MYSQL数据库死锁

发表于 2020-09-14 | 更新于 2021-11-18 | 分类于 数据库 , MYSQL
  • 排它锁(Exclusive Locks,即X锁)其他的事务不能对它读取和修改
  • 共享锁(Share Locks,即S锁)数据对象可以被其他事务读取,但不能修改

死锁的第一种情况

一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。

解决方法:

这种死锁比较常见,是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进 行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源。

死锁的第二种情况

用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A 有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。这种死锁比较隐蔽,但在稍大点的项 目中经常发生。如在某项目中,页面上的按钮点击后,没有使按钮立刻失效,使得用户会多次快速点击同一按钮,这样同一段代码对数据库同一条记录进行多次操 作,很容易就出现这种死锁的情况。

解决方法:

1、对于按钮等控件,点击后使其立刻失效,不让用户重复点击,避免对同时对同一条记录操作。
2、使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是 通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数 据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。乐观锁机制避免了长事务中的数据 库加锁开销(用户A和用户B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。需要注意的是,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户更新操作不受我们系统的控制,因此可能会造 成脏数据被更新到数据库中。
3、使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统, 当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户账户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读 出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对成百上千个并发,这 样的情况将导致灾难性的后果。所以,采用悲观锁进行控制时一定要考虑清楚。

死锁的第三种情况

如果在事务中执行了一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。类似的情 况还有当表中的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来越慢,最终发生阻塞或死锁。

解决方法:

SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。

数据库三范式

发表于 2020-09-14 | 更新于 2021-11-18 | 分类于 数据库 , 基础
  • 第一范式:数据表中的每一列(每个字段)必须是不可拆分的最小单元,也就是确保每一列的原子性;(用字段存JSON结构,是一种很操蛋的事情)
  • 第二范式(2NF):满足1NF后,要求表中的所有列,都必须依赖于主键,而不能有任何一列与主键没有关系,也就是说一个表只描述一件事情;(外键基本不会用)
  • 第三范式:必须先满足第二范式(2NF),要求:表中的每一列只与主键直接相关而不是间接相关,(表中的每一列只能依赖于主键);(总会建一些冗余字段)

MySQL数据结构

发表于 2020-09-14 | 更新于 2021-11-18 | 分类于 数据库 , MYSQL

数据结构

  • InnoDB 聚簇索引 数据和索引B+树存储在一起

InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形

  • MyISM 非聚簇索引 主键B+树在叶子节点存储指向真正数据行的指针,而非主键

alt

  • 辅助索引B+树中检索Name,到达其叶子节点获取对应的主键
  • 使用主键在主索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据

储存主键ID而不直接存储地址值,这样避免行移动或者数据页分裂时辅助索引的维护工作。

alt
Page结构 InnoDB存储的最基本构件(最小单位)
InnoDB最大64TB的存储容量(16Kib * 2^32 = 64Tib)

题外话

如果数据不更改,使用MyISM性能也不错,但是数据一点不变,为什么不用redis呢?

MYSQL8 各方面超越5.7 如果新选择不要考虑5.7

窗口函数

多线程各方面性能

降序索引 DESC

主键不要使用uuid之类的,索引树的特性而要分裂、调整节点,十分低效。
辅助索引占用更多的空间,因为辅助索引保存主键的值。

自适应哈希索引(Adaptive Hash Index, AHI) 有一些触发条件创建

ElasticSearch

发表于 2020-09-14 | 更新于 2021-08-02 | 分类于 中间件 , ES

使用场景

日志分析、时序分析、全文检索,大数据的一些聚合查询

基本原理

底层 Lucene 高效的信息检索,利用倒排索引,存储使用LSM数据模型

alt
alt
alt

ES 读

Elasticsearch查询的时候需要查询所有Shard

ES 写

alt

内存模型

FST 倒排索引 占据了绝大部分堆内内存(优化到堆外存储)
alt

题外话

Lucene 没有更新的概念。ES 并不适合高频率更新文档操作,会导致效率极低。
插入数据如果带ID,也会导致类UPDATE操作,效率降低至少1倍。

各类资料

各个版本基本描述文档:
https://www.elastic.co/guide/en/elasticsearch/reference/6.5/getting-started.html

文档汇总:
https://www.elastic.co/guide/index.html

官方中文:
https://www.elastic.co/guide/cn/index.html

JavaAPI
https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html

JAVA 客户端API
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/6.5/java-rest-high-put-template.html

底层进阶:
https://cloud.tencent.com/developer/article/1626250
https://cloud.tencent.com/developer/column/2428

日常阅读:
https://elasticsearch.cn/article/

plugin 模式开发
https://www.cnblogs.com/wangnanhui/articles/10413066.html
https://github.com/wangnanhui/elastic-plugin-test

Spring Bean添加启动逻辑

发表于 2020-09-14 | 更新于 2020-09-16 | 分类于 Spring , 开发
  • Spring启动时执行逻辑:
  • Javax的@PostConstruct
  • InitializingBean
  • ApplicationListener
  • @Bean和initMethod
  • Spring Boot为CommanLineRunner
  • Spring Boot ApplicationRunner

启动顺序:

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
@Component
@Scope(value = "prototype")
public class AllStrategiesExampleBean implements InitializingBean {

private static final Logger LOG
= Logger.getLogger(AllStrategiesExampleBean.class);

public AllStrategiesExampleBean() {
LOG.info("Constructor");
}

@Override
public void afterPropertiesSet() throws Exception {
LOG.info("InitializingBean");
}

@PostConstruct
public void postConstruct() {
LOG.info("PostConstruct");
}

public void init() {
LOG.info("init-method");
}
}

[main] INFO o.b.startup.AllStrategiesExampleBean - Constructor
[main] INFO o.b.startup.AllStrategiesExampleBean - PostConstruct
[main] INFO o.b.startup.AllStrategiesExampleBean - InitializingBean
[main] INFO o.b.startup.AllStrategiesExampleBean - init-method

Try括号代码逻辑

发表于 2020-09-14 | 更新于 2020-09-18 | 分类于 JAVA , 开发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
try (
InputStream fis = new FileInputStream(source);
OutputStream fos = new FileOutputStream(target)
){
byte[] buf = new byte[8192];

int i;
while ((i = fis.read(buf)) != -1) {
fos.write(buf, 0, i);
}
}
catch (Exception e) {
e.printStackTrace();
}
  • try括号内的资源会在try语句结束后自动释放,前提是这些可关闭的资源必须实现 java.lang.AutoCloseable 接口。
  • InputStream 和OutputStream 父类中一定实现了AutoCloseable接口
  • ThreadLocal 作为属性 放到一个实现java.lang.AutoCloseable 接口类里,实现自动清理
1…151617…21

Kamafeel

204 日志
57 分类
129 标签
© 2022 Kamafeel
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Pisces v7.1.2