0%

ReentrantLock 是可重入的独占锁, 同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞而被放入该锁的AQS 阻塞队列里面。

ReentrantLock 根据参数来决定其内部是一个公平还是非公平锁,默认是非公平锁。其中Sync 类直接继承自AQS , 它的子类NonfairSyncFairSync 分别实现了获取锁的非公平与公平策略。

1
2
3
4
5
6
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
阅读全文 »

AbstractQueuedSynchronizer 抽象同步队列简称AQS ,它是实现同步器的基础组件,并发包中锁的底层就是使用AQS 实现的。另外,大多数开发者可能永远不会直接使用AQS ,但是知道其原理对于架构设计还是很有帮助的。

阅读全文 »

CopyOnWriteArrayList 是一个线程安全的ArrayList ,对其进行的修改操作都是在底层的一个复制的数组(快照)上进行的,也就是使用了写时复制策略。

image-20211021195250084

CopyOnWriteArrayList 的类图中,每个CopyOnWriteArrayList 对象里面有一个使用volatile修饰的array 数组对象用来存放具体元素,独占锁lock用来保证同时只有1个线程对array进行修改。

阅读全文 »

JUC 包提供了一系列的原子性操作类,这些类都是使用非阻塞算法CAS 实现的,相比使用锁实现原子性操作这在性能上有很大提高。由于原子性操作类的原理都大致相同,所以本文只讲解最简单的AtomicLong 类及JDK 8 中新增的LongAdder 的实现原理。

阅读全文 »

Random类的局限性

每个Random 实例里面都有一个原子性的种子变量用来记录当前的种子值,当要生成新的随机数时需要根据当前种子计算新的种子并更新回原子变量。在多线程下使用单个Random 实例生成随机数时,当多个线程同时计算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS 操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试, 这会降低并发性能,所以ThreadLocalRandom 应运而生。

阅读全文 »

synchronized

synchronized块是Java 提供的一种原子性内置锁, Java 中的每个对象都可以把它当作一个同步锁来使用, 这些Java 内置的使用者看不到的锁被称为内部锁,也叫作监视器锁。synchronized 关键字底层原理属于 JVM 层面。线程的执行代码在进入synchronized 代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的wait 系列方法时释放该内置锁。内置锁是排它锁,也就是当一个线程获取这个锁后, 其他线程必须等待该线程释放锁后才能获取该锁。

阅读全文 »

线程封闭

当访问共享变量时,往往需要加锁来保证数据同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程中访问数据,就不需要同步了,这种技术称为线程封闭。

线程封闭技术有一个常见的应用,JDBC的Connection对象。Connection对象在实现的时候并没有对线程安全做太多的处理,JDBC的规范里也没有要求Connection对象必须是线程安全的。实际在服务器应用程序中,线程从连接池获取了一个Connection对象,使用完再把Connection对象返回给连接池,由于大多数请求都是由单线程采用同步的方式来处理的,并且在Connection对象返回之前,连接池不会将它分配给其他线程。因此这种连接管理模式处理请求时隐含的将Connection对象封闭在线程里面,这样我们使用的Connection对象虽然本身不是线程安全的,但是它通过线程封闭也做到了线程安全。

实现线程封闭,一般有三种方法:Ad-hoc 线程封闭、堆栈封闭、ThreadLocal线程封闭

阅读全文 »

什么是线程

在讨论什么是线程前有必要先说下什么是进程,因为线程是进程中的一个实体,线程本身是不会独立存在的。进程是代码在数据集合上的一次运行活动, 是系统进行资源分配和调度的基本单位, 线程则是进程的一个执行路径, 一个进程中至少有一个线程,进程中的多个线程共享进程的资源。操作系统在分配资源时是把资源分配给进程的, 但是CPU 资源比较特殊, 它是被分配到线程的, 因为真正要占用C PU 运行的是线程, 所以也说线程是CPU 分配的基本单位。

阅读全文 »

共享Handler

1
2
3
4
5
6
7
8
9
10
11
serverBootstrap
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new Spliter());//帧解码器
ch.pipeline().addLast(new PacketDecoder());//数据包解码器
ch.pipeline().addLast(new HandlerA());//业务Handler
ch.pipeline().addLast(new HandlerB());//业务Handler
ch.pipeline().addLast(new HandlerC());//业务Handler
ch.pipeline().addLast(new PacketEncoder());//数据包编码器
}
});

每次有新连接到来的时候,都会调用 ChannelInitializerinitChannel() 方法,然后这里相关的 handler 都会被 new 一次。许多 handler,他们内部都是没有成员变量的,也就是说是无状态的,我们完全可以使用单例模式,即调用 pipeline().addLast() 方法的时候,都直接使用单例,不需要每次都 new,提高效率,也避免了创建很多小的对象。

单例改造:对于无状态Handler,使用单例模式,多个channel共享一个实例

阅读全文 »

1. 网络问题

下图是网络应用程序普遍会遇到的一个问题:连接假死

image.png

连接假死的现象是:在某一端(服务端或者客户端)看来,底层的 TCP 连接已经断开了,但是应用程序并没有捕获到,因此会认为这条连接仍然是存在的,从 TCP 层面来说,只有收到四次握手数据包或者一个 RST 数据包,连接的状态才表示已断开。

连接假死会带来以下两大问题

阅读全文 »