AQS(上) 同步队列AQS介绍篇

AQS——锁的底层支持

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

image-20200420175900001.png

由该图可以看到,AQS是一个FIFO的双向队列,其内部通过节点headtail记录队首和队尾元素,队列元素的类型为Node

阅读更多

LockSupport 工具类使用以及实现原理

JDK中的rt.jar包里面的是个LockSupport是个工具类,它的主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础。

LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。LockSupport是使用Unsafe类实现的,下面介绍LockSupport中的几个主要函数。

阅读更多

LongAccumulator类原理探究

LongAccumulator类原理探究

LongAdder类时LongAccumulator的一个特例,LongAccumulator比LongAdder的功能更强大。例如下面的构造函数,其中accumulatorFunction是一个双目运算器接口,其根据输入的两个参数返回一个计算值,identity则是LongAccumulator累加器的初始值。

1
2
3
4
5
6
7
8
9
10
11
public LongAccumulator(LongBinaryOperator accumulatorFunction,
long identity) {
this.function = accumulatorFunction;
base = this.identity = identity;
}

public interface LongBinaryOperator {

// 根据两个参数计算并返回一个值
long applyAsLong(long left, long right);
}
阅读更多

LongAdder(下)使用及代码分析

LongAdder代码分析

为了解决高并发下多线程对一个变量CAS争夺失败后进行自旋而造成的降低并发性能的问题,LongAdder在内部维护多个Cell元素(一个动态的Cell数组)来分担单个变量进行争夺开销。下面围绕以下话题从源码角度来分析LongAdder的实现!

  1. LongAdder的结构是怎样的?
  2. 当前线程应该访问Cell数组里面哪一个Cell元素?
  3. 如何初始化Cell数组?
  4. Cell数组如何扩容?
  5. 线程访问分配的Cell元素有冲突后如何处理?
  6. 如何保证线程操作被分配的Cell元素的原子性?
阅读更多

LongAdder(上)介绍篇

之前有篇文章讲过AtomicLong通过CAS提供了非阻塞的原子性操作,相比使用阻塞算法的同步器来说它的性能已经很好了,但是JDK开发组并不满足于此。使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS操作,而这会白白浪费CPU资源。

因此JDK8新增了一个原子性递增或者递减类LongAdder用来克服在高并发下使用AtomicLong的缺点。既然AtomicLong的性能瓶颈是由于多线程同时去竞争一个变量的更新而产生的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源,是不是就解决了性能问题?是的,LongAdder就是这个思路。下面通过一张的图来理解两者设计的不同之处。

image-20200411011831604.png

阅读更多

ThreadLocal(下)继承性问题解决及实现原理

ThreadLocal不支持继承性

首先看一个例子

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

// (1) 创建线程变量
public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

public static void main(String[] args) {
// (2) 设置线程变量
threadLocal.set("hello world");
// (3) 启动子线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// (4) 子线程输出线程变量的值
System.out.println("thread: " + threadLocal.get());
}
});

thread.start();

// (5) 主线程获取并输出threadLocal的值
System.out.println("main: " + threadLocal.get());
}
}
阅读更多

ThreadLocal(中)实现原理如何做到本地化

ThreadLocal实现原理

下面是ThreadLocal相关类的类结构图,如图:

image-20200407164556036.png

由该图可知,Thread类中有一个threadLocals和一个inheritableThreadLocals,它们都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的HashMap。在默认情况下,每个线程中的这两个变量都为null,

阅读更多

ThreadLocal(上)介绍及使用篇

出现背景:

多线程访问同一个共享变量特别容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时,为了保证线程安全,一般使用者在访问共享变量时进行适当的同步。如图所示

image-20200323150913

同步的措施一般是加锁,这就需要使用者对锁有一定的了解,这就显然加重了使用者的负担,那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢?ThreadLocal他可以,虽然他不是为了解决这个问题出现的。

阅读更多

线程的通知与等待

Java中的Object类是所有类的父类,鉴于继承机制,Java把所有的类都需的方法放在了Object类里面,其中就包含要说的通知与等待。

1.wait()方法

当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回。

​ 1.其他线程调用了该共享对象的 notify() *或者 *notifyAll() 方法。

​ 2.其他线程调用了该线程的 interrupt() 方法,该线程抛出 InterruptedException 异常返回

另外需要注意的是,如果调用 wait() 方法的线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程会抛出 *IllegalMonitorStateException *异常。

那么一个线程如何才能获取一个共享变量的监视器锁呢?

阅读更多

CountDownLatch介绍及使用

CountDownLatch作用阻塞一个或多个线程等待其他线程完成操作。

定义初始化的时候,需要传入一个正数来初始化计数器(0也可以,但这样定义没有实际意义)。有两个方法countDown()用于递减计数器,await()方法阻塞当前线程,直到计数器递减为0

CountDownLatch通常用于多个线程之间的协调工作。

假设有如下情节:

同时获取5张表的数据并一同返回

为了让cpu更好的得到利用,程序执行效率更高,使用多线程来完成。

看如下代码:

阅读更多