12k 11 分钟

FutureTask 为 Future 提供了基础实现(言外之意就是也提供了一些功能性函数供我们创建自定义 task 类使用),如获取任务执行结果 ( get ) 和取消任务 ( cancel ) 等。如果任务尚未完成,获取任务执行结果时将会阻塞。一旦执行结束,任务就不能被重启或取消 (除非使用 runAndReset 执行计算)。 FutureTask 常用来封装 Callable 和 Runnable ,也可以作为一个任务提交到线程池中执行。 FutureTask 的线程安全由 CAS 来保证。 # 异步模型 在 Java...
4k 4 分钟

# Executor 框架 Java 线程创建和销毁需要一定开销,池化技术避免了频繁的创建和销毁,通过向线程池传入任务重复利用线程。Java 的线程既是工作单元,也是执行机制。JDK1.5 开始将工作单元和执行机制分开,工作单元是 Runnable 和 Callable ,执行机制由 Executor 框架提供。 Executor 框架主要由三部分组成: 任务:任务需要实现 Runnable 或 Callable 。 任务的执行: Executor 和 ExecutorService 接口。 异步计算的结果:接口 Future 和实现该接口的 FutureTask 类 Executor...
4.9k 4 分钟

# ReentrantLock Reentrantlock 是可重入锁,内部实现了公平锁,也实现了非公平锁。 其实之前讲解 AQS 中实现的自定义锁就是一个可重入非公平锁,只不过 ReentrantLock 内部实现了公平与非公平两种锁。 一个新线程调用 tryAcquire 可以直接抢锁,而不需要管阻塞队列中是否有其他线程等待拿锁。这就是非公平的。 ReentrantLock 的公平实现: protected final boolean tryAcquire(int acquires) { final Thread current =...
14k 12 分钟

# 基本使用 在 locks 包下有一个类是 AbstractQueuedSynchronizer ,其简写就是 AQS , AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器。 AQS 是一个提供给用户自定义同步器的简单框架。其内部严格使用先进先出的阻塞队列,并且构造出来的同步器依赖于一个 int 类型的 state 来判断是否有线程占用锁。 AQS 提供独占模式( exclusive )和共享模式( shared ),用户自定义的同步器一般支持一种模式,当然有些同步器两种也支持。 从本质上说, AQS 实现同步的底层逻辑原理和...
4.7k 4 分钟

# 接口设计 在讲解操作系统篇的时候,很多 c 语言的伪代码,并没有什么 synchronized 关键字什么的,其上锁 / 解锁的步骤就是 lock/unlock ,方法,等待 / 唤醒也就是 wait/signal ,在 j.u.c 包中也就提供了这样的 API 。 # Lock public interface Lock { // 获取锁,拿不到锁会阻塞,等待其他线程释放锁,获取到锁后返回 void lock(); // 同上,但是等待过程中会响应中断 void lockInterruptibly() throws InterruptedException; //...
5.4k 5 分钟

# 静态类 我们先介绍一下静态类,这在之后的讲解会用到。静态类只能是内部类,如果外部类使用 static 会报错。 首先,静态类被 static 修饰,静态代码(方法,属性等)的使用方式不依赖于实例对象。静态类中可以有非静态属性,要使用非静态属性就需要依赖实例对象,而且这个非静态属性并不是所有类共享。 public class Test { static class A { private int count = 0; public void get() { System.out.println(count); }...
4k 4 分钟

# Java 内存模型 并发编程中,线程通信一般有两种方式:共享内存和消息传递。Java 并发采用的是共享内存模型,所有实例域,静态域,数组元素都存储在堆内存中,堆内存在线程之间共享。 每个线程都有一个本地内存,其实这个本地内存是一个抽象概念,它包含了缓存,写缓冲区,寄存器, cache 等,和 volatile 关键字那部分知识类似。线程之间的共享变量存储在主内存中。 下图为 JMM 抽象结构: JMM 通过控制主内存与每个线程的本地内存之间的交互,提供内存可见性的保证。 # 重排序 编译器和处理器常常会对指令重排序: 编译器优化重排序:在 Java...
6.8k 6 分钟

# volatile 关键字 CPU 并不是和内存直接交互,而是和 cache 高速缓存交互,当需要访问一个内存地址时,如果这个地址之前被读到 cache 中,就直接从 cache 中拿,这叫做缓存命中。同时,如果想要修改一个数据,而这个数据被读到了 cache 中,处理器则会将这个操作数写回到缓存中,而不是内存中,这叫做写命中。 这也导致,在多处理器中,多个线程各自运行在自己的处理器上,对共享变量各自都在 cache 中有一份副本,某一线程对该变量修改后是写回缓存中,而不是马上写回内存,即使写回内存,其他线程也不会读取新的值(因为自己的 cache...
3.9k 4 分钟

# 线程 本篇讲解 Java 的线程基础。 谈起线程的状态(生命周期),操作系统层面上和 Java 层面上是不同的: 操作系统:初始状态(NEW),可运行状态(READY),运行状态(RUNNING),等待(WAITING),终止状态(TERMINATED)。 Java:新建(New),可运行(Runable),阻塞(Blocking),无限等待(Waiting),限时等待(Timed Waiting),死亡(Terminated)。 在操作系统篇中,我们提到的拿不到锁休眠(阻塞),等待条件变量进入等待队列休眠,在操作系统层面都是 WAITING。 注意: Java 没有...
2.2k 2 分钟

# 并发缺陷 并发的缺陷主要分为死锁和非死锁缺陷。 # 非死锁缺陷 《操作系统导论》中基于 Lu 及其同事的研究,讨论了违反原子性缺陷和错误顺序缺陷。 违反原子性缺陷: mysql 中出现过的例子: Thread 1::if(thd->proc_info) { //.... fputs(thd->proc_info,..);}Thread 2::thd->proc_info = NULL;如果线程 1 在 fputs 前发生中断,线程 2 将 proc_info 设为空,再执行线程 1...