继承 Thread 类
public class SubThread extends Thread {
//重写 run()方法
public void run() {
//输出...
}
}
public class TestThread {
public static void main(String[] args) {
SubThread st = new SubThread();
//调用start()方法运行线程
st.start();
}
}
实现 Runnable 接口
public class PrintNum1 implements Runnable {
public void run() {
...
}
}
public class TestThread1 {
public static void main(String[] args) {
PrintNum1 p = new PrintNum1();
Thread t1 = new Thread(p);
t1.start();
}
}
实现 Callable 接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
Api:
概述:
优先级高只能是说明,它获得时间片的概率大,但不是一定会执行它
线程创建时继承父线程的优先级
1(MIN_PRIORITY)5(NORMAL_PRIORITY)10(MAX_PRIORITY)
void interrupt():向线程发送中断请求。线程的中断状态将被设置为 true。如果该线程被 sleep 调用阻塞、限期等待或者无限期等待状态,抛出 InterruptedException 异常。
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000);
} catch(InterruptedException e) {
e.printStackTrace();
}
}).start();
// 线程在 sleep 时中断,抛出异常
thread.interrupt();
System.out.println("Main run");
}
static boolean interrupted():测试当前线程是否被中断,并将当前线程的中断状态重置为 false
boolean isInterrupted():测试线程是否被终止,不改变线程的中断状态
新建、就绪、运行、阻塞、死亡
【细谈 Java 并发】谈谈线程池:ThreadPoolExecutor
线程数计算公式: 线程数 = CPU 核数 * 期望 CPU 使用率 0~1 *(1 + 等待时间 / 计算时间)
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
allowCoreThreadTimeOut
。corePoolSize
时,非核心线程在终止之前等待新任务的最大时间。keepAliveTime
参数的时间单位。execute
方法提交的 Runnable
任务。提交的线程后若发现线程总数超过 corePoolSize
但是不超过 keepAliveTime
的情况下。随着任务不断增加
execute(Runnable runnable):开启线程
shutdown():会等待线程都执行完毕之后再关闭
shutdownNow():相当于调用每个线程的 interrupt() 方法
submit():如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。
Future<?> future = executorService.submit(() -> {
// ...
});
future.cancel(true);
RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务
SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown() 方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用 shutdown() 方法进入该状态)
STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态
TIDYING:如果所有的任务都已终止了,workerCount(有效线程数)为 0,线程池进入该状态后会调用 terminated() 方法进入 TERMINATED 状态
TERMINATED:在 terminated() 方法执行完后进入该状态,默认 terminated() 方法中什么也没有做
ctl:
高 3 位表示线程池状态,低 29 位表示 worker 数量
// 1. 状态|工作数的一个 32 bit 的值
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 2. 29 bit 代表线程数
private static final int COUNT_BITS = Integer.SIZE - 3;
// 3. 线程池允许的最大线程数。1 左移 29 位,然后减 1,即为 2^29 - 1
// 0001-1111-1111-1111-1111-1111-1111-1111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 4. 线程池有 5 种状态,按大小排序如下:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
// 1110-0000-0000-0000-0000-0000-0000-0000
private static final int RUNNING = -1 << COUNT_BITS;
// 0000-0000-0000-0000-0000-0000-0000-0000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 0010-0000-0000-0000-0000-0000-0000-0000
private static final int STOP = 1 << COUNT_BITS;
// 0100-0000-0000-0000-0000-0000-0000-0000
private static final int TIDYING = 2 << COUNT_BITS;
// 0110-0000-0000-0000-0000-0000-0000-0000
private static final int TERMINATED = 3 << COUNT_BITS;
// 5. 获取线程池状态,通过按位与操作,低 29 位将全部变成 0
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 6. 获取线程池 worker 数量,通过按位与操作,高 3 位将全部变成 0
private static int workerCountOf(int c) { return c & CAPACITY; }
// 7. 根据线程池状态和线程池 worker 数量,生成 ctl 值
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 8. 线程池状态小于 xx
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
// 9. 线程池状态大于等于 xx
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
execute(Runnable command) 方法:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1.worker 数量 < 核心线程数 -> 直接创建 worker 执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
// 没有成功 addWorker(),再次获取 c(凡是需要再次用 ctl 做判断时,都会再次调用ctl.get())
c = ctl.get();
}
// 2.worker 数量超过核心线程数 && 线程池是运行状态 -> 任务直接进入队列
if (isRunning(c) && workQueue.offer(command)) {
// 任务入队列前后,线程池的状态可能会发生变化。
int recheck = ctl.get();
// 线程池状态不是 RUNNING 状态,说明执行过 shutdown 命令,需要对新加入的任务执行 reject() 操作。
if (! isRunning(recheck) && remove(command))
reject(command);
// 在线程池构造方法中,核心线程数允许为 0
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建 worker 执行任务。
// 1. 线程池不是运行状态时,addWorker 内部会判断线程池状态
// 2. addWorker 第 2 个参数表示是否创建核心线程
// 3. addWorker 返回 false,则说明任务执行失败,需要执行 reject 操作
else if (!addWorker(command, false))
reject(command);
}
addworker(Runnable firstTask, boolean core) 方法:
ThreadPoolExecutor#runWorker
方法private boolean addWorker(Runnable firstTask, boolean core) {
retry:
// 外层自旋
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 这个条件和下面的条件等价
// (rs > SHUTDOWN) ||
// (rs == SHUTDOWN && firstTask != null) ||
// (rs == SHUTDOWN && workQueue.isEmpty())
// 1. 线程池状态大于 SHUTDOWN 时,直接返回 false
// 2. 线程池状态等于 SHUTDOWN,且 firstTask 不为 null,直接返回 false
// 3. 线程池状态等于 SHUTDOWN,且队列为空,直接返回 false
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 内层 CAS 自旋
for (;;) {
int wc = workerCountOf(c);
// worker 数量超过容量,直接返回 false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 使用 CAS 的方式增加 worker 数量。
// 若增加成功,则直接跳出外层循环进入到第二部分
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// 线程池状态发生变化,对外层循环进行自旋
if (runStateOf(c) != rs)
continue retry;
// 其他情况,直接内层循环进行自旋即可
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
// worker 的添加必须是串行的,因此需要加锁
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
// 这儿需要重新检查线程池状态
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// worker 已经调用过了 start() 方法,则不再创建 worker
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// worker 创建并添加到 workers 成功
workers.add(w);
// 更新`largestPoolSize`变量
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 启动 worker 线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// worker 线程启动失败,说明线程池状态发生了变化(关闭操作被执行),需要进行shutdown 相关操作
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker 类:
既是一个同步队列也是一个 Runnable
自身维护了一个 Thread thread
(任务执行器)和一个 Runnable firstTask
(任务)
在构造器中会把传入的任务赋值给 firstTask
,然后把当前自己传入 thread
当调用 thread.start()
时会执行这个 worker 的 run() 方法,最终调用 ThreadPoolExecutor#runWorker
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 这儿是 Worker 的关键所在,使用了线程工厂创建了一个线程。传入的参数为当前worker
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
...
}
runworker(Worker w):核心线程执行逻辑
w.lock();
。Worker 实现了 AQS,所以自己也是一把锁,并重写了 tryAcquire 方法(非重入)task.run();
w.completedTasks++;
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 调用 unlock() 是为了让外部可以中断
w.unlock(); // allow interrupts
// 这个变量用于判断是否进入过自旋(while 循环)
boolean completedAbruptly = true;
try {
// 这儿是自旋
// 1. 如果 firstTask 不为 null,则执行 firstTask;
// 2. 如果 firstTask 为 null,则调用 getTask() 从队列获取任务。
// 3. 阻塞队列的特性就是:当队列为空时,当前线程会被阻塞等待
while (task != null || (task = getTask()) != null) {
// 这儿对 worker 进行加锁,是为了达到下面的目的
// 1. 降低锁范围,提升性能
// 2. 保证每个 worker 执行的任务是串行的
// 开始运行,不允许中断
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
// 如果线程池正在停止,则对当前线程进行中断操作
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
// 执行任务,且在执行前后通过 beforeExecute() 和 afterExecute() 来扩展其功能。
// 这两个方法在当前类里面为空实现。
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
// 帮助 gc
task = null;
// 已完成任务数加一
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 自旋操作被退出,说明线程池正在结束
processWorkerExit(w, completedAbruptly);
}
}
概述:
ForkJoin 使用 ForkJoinPool 来启动,它是一个特殊的线程池,线程数量取决于 CPU 核数
主要用于并行计算中,和 MapReduce 原理类似,都是把大的计算任务拆分成多个小任务并行计算
原理:
ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线程都维护了一个双端队列,用来存储需要执行的任务。工作窃取算法允许空闲的线程从其它线程的双端队列中窃取一个任务来执行。
窃取的任务必须是最晚的任务,避免和队列所属线程发生竞争
不带返回值
public class AddTask extends RecursiveAction {
int start, end;
AddTask(int s, int e) {
start = s;
end = e;
}
@Override
protected void compute() {
if (end - start <= MAX_NUM) {
long sum = 0L;
for (int i = start; i < end; i++) {
sum += nums[i];
}
System.out.println("from:" + start + " to:" + end + " = " + sum);
} else {
int middle = start + (end - start) / 2;
AddTask subTask1 = new AddTask(start, middle);
AddTask subTask2 = new AddTask(middle, end);
subTask1.fork();
subTask2.fork();
}
}
public static void main(String[] args) {
ForkJoinPool fjp = new ForkJoinPool();
AddTask task = new AddTask(0, nums.length);
fjp.execute(task);
}
}
带返回值
public class ForkJoinExample extends RecursiveTask<Integer> {
private final int threshold = 5;
private int first;
private int last;
public ForkJoinExample(int first, int last) {
this.first = first;
this.last = last;
}
@Override
protected Integer compute() {
int result = 0;
if (last - first <= threshold) {
// 任务足够小则直接计算
for (int i = first; i <= last; i++) {
result += i;
}
} else {
// 拆分成小任务
int middle = first + (last - first) / 2;
ForkJoinExample leftTask = new ForkJoinExample(first, middle);
ForkJoinExample rightTask = new ForkJoinExample(middle + 1, last);
leftTask.fork();
rightTask.fork();
result = leftTask.join() + rightTask.join();
}
return result;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinExample example = new ForkJoinExample(1, 10000);
ForkJoinPool forkJoinPool = new ForkJoinPool();
Future result = forkJoinPool.submit(example);
System.out.println(result.get());
// forkJoinPool.execute(example);
// long result = example.join();
// System.out.println(result);
}
}
CachedThreadPool: 一个任务创建一个线程
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService.execute(new MyRunnable());
}
executorService.shutdown();
}
FixedThreadPool: 所有任务只能使用固定大小的线程
SingleThreadExecutor: 相当于大小为 1 的 FixedThreadPool
WorkStealingPool:工作窃取线程池
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
多个线程不管以何种方式访问某个类,并且在主调代码中不需要进行同步,都能表现正确的行为
不可变(Immutable)的对象一定是线程安全的。
final 关键字修饰的基本数据类型
String
枚举类型
Number 部分子类,如 Long 和 Double 等数值包装类型,BigInteger 和 BigDecimal 等大数据类型。但同为 Number 的原子类 AtomicInteger 和 AtomicLong 则是可变的
**不可变集合:**可以使用 Collections.unmodifiableXXX() 方法来获取一个不可变的集合。对集合进行修改的方法都直接抛出异常。
public class ImmutableExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(map);
unmodifiableMap.put("a", 1);
}
}
/* Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableMap.put(Collections.java:1457)
at ImmutableExample.main(ImmutableExample.java:9) */
阻塞同步:悲观锁:synchronized 和 ReentrantLock
非阻塞同步
如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性
栈封闭:
多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的
ThreadLocal:
把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题
可重入代码:
这种代码也叫做 纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。
可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。
synchronized 发生异常会释放锁
不能拿 String、Integer、Long 等基础类型做锁
synchronized 和 Lock 能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。但是同步块里的非原子操作依旧可能发生指令重排
同步方法:
锁为当前对象,即 this,所以不能用在继承方法的线程上
public class Bank {
private double[] accounts;
public synchronized void transfer(int from, int to, int amount) throws InterruptedException {
while (accounts[from] < amount) {
wait();
}
accounts[from] -= amount;
accounts[to] += amount;
notifyAll();
}
}
同步阻塞:
public class Window2 implements Runnable {
// 共享数据
int ticket = 100;
// 锁(同步监视器)
Object obj = new Object();
// 所有线程必须共用同一把锁
public void run() {
while (true) {
// 若是静态方法可用当前类.class
synchronized (obj) {
if (ticket > 0) {
try {
Thread.currentThread().sleep(10);
}
catch (InterrupteException e) {
e.printStackTrace();
}
System.out.println(...);
}
}
}
}
}
synchronized 加锁需要程序由用户态切换到内核态,效率低
Intel x86 架构的 CPU 来说一共有 0~3 四个特权级,0 级最高,3 级最低。
硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查。相关的概念有 CPL、DPL 和 RPL。
Linux 中当程序运行在 3 级特权级上时称之运行在用户态,运行在 0 级特权级上时称之运行在内核态。
用户态通过申请外部资源(申请堆内存、读写磁盘文件。。。)切换至内核态
用户态切换到内核态的 3 种方式:
具体的切换操作: 以 open 函数调用为例
open 函数调用时,会通过中断陷入内核,从而调用 sys_open 函数。
系统调用触发 0x80 中断: 并且将系统调用号存储在 eax 寄存器中,然后陷入内核,内核开始执行中断处理程序,在系统调用表中查找系统调用号对应的系统内核函数并且调用,执行完成后又将返回值通过 eax 寄存器返回给用户空间
中断机制: 中断处理程序(内核 )
计算机处于执行期间,系统内发生了非寻常或非预期的急需处理事件,CPU 暂时中断当前正在执行的程序而转去执行相应的事件处理程序,处理完毕后返回原来被中断处继续执行
处理中断源的程序称为中断处理程序。中断的实现由软件和硬件综合完成,硬件部分叫做硬件装置,软件部分称为软件处理程序。
对象头 markword 记录了
锁升级步骤:
偏向锁假定将来只有第一个申请锁的线程会使用锁。因此,只需要在 Markword 中 CAS 记录 当前线程指针,如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁。
以后当前线程记录的这个线程就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁
源码: fast_enter 方法中在 safepoint 的时候上锁,失败则调用 slow_enter 方法升级为自旋锁。
在明确知道会有多个线程竞争的情况下,偏向锁会涉及锁撤销,比自旋锁效率低,所以这时不会启用偏向锁
例如:JVM 启动过程会有很多线程竞争,所以默认情况启动时不打开偏向锁,过一段时间才会打开
-XX:BiasedLockingStartupDelay=4(默认 4 秒),刚开始未偏向任何一个线程,所以称为匿名偏向
轻量级锁是相对于传统的重量级锁而言,它使用 CAS 操作来避免重量级锁使用互斥量的开销。对于绝大部分的锁,在整个同步周期内都是不存在竞争的,因此也就不需要都使用互斥量进行同步。详情查看自旋锁。
原理:
每个线程在其线程栈内部生成一个 LockRecord,拿到锁后记录在 markword 中。即演化为多个 LockRecord 使用 CAS 竞争写入 markword 的场景。
对应 slow_enter 方法,首先进入自旋,自旋锁不行则调用 inflate 方法膨胀为重量级锁。
升级:
synchronized 编译为字节码后代码由 monitorenter
和 monitorexit
包围,表示上锁和释放锁。
synchronized 加锁需要程序由用户态切换到内核态,效率低。
内核态 ObjectMonitor 对象中有 WaitSet 队列,抢锁的线程都会进入等待队列,不需要消耗 CPU 资源,由操作系统进程调度
重入次数必须记录,因为加锁次数必须和解锁次数对应
锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除
如下 concatString 方法:
每个 append() 方法中都有一个 synchronized 同步块。虚拟机观察变量 sb,很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说,sb 的所有引用永远不会逃逸到 concatString() 方法之外,其他线程无法访问到它,因此可以进行消除
public static String concatString(String s1, String s2, String s3) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}
原理: 锁消除主要是通过逃逸分析来支持,如果堆上的共享数据不可能逃逸出去被其它线程访问到,那么就可以把它们当成私有数据对待,也就可以将它们的锁进行消除
如果一系列的连续操作都对同一个对象反复加锁和解锁,频繁的加锁操作就会导致性能损耗
虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁,则会把加锁的范围扩展(粗化)到整个操作序列的外部。
Monitor 是一种用来实现同步的工具
与每个 java 对象相关联,所有的 Java 对象是天生携带 monitor
Monitor 是实现 Sychronized(内置锁)的基础
Monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现
ObjectMonitor() {
// 用来记录该对象被线程获取锁的次数
_count = 0;
_waiters = 0;
// 锁的重入次数
_recursions = 0;
// 指向持有 ObjectMonitor 对象的线程
_owner = NULL;
// 处于 wait 状态的线程,会被加入到 _WaitSet
_WaitSet = NULL;
_WaitSetLock = 0 ;
// 处于等待锁 block 状态的线程,会被加入到该列表
_EntryList = NULL ;
}
作用: 条件对象用来管理那些已经进入被保护的代码段但还不能运行的线程。一个 Condition 对象为一个队列。
方法:
ReentrantLock <==> synchronized
Condition sufficientFunds = bankLock.newCondition();
// 当前线程不满足条件,进入该条件的等待集,放弃锁
while (!(ok to proceed)) {
sufficientFunds.await();
}
// do something
// 其他线程的操作应使该线程重新判断条件。
// 重新激活因为这一条件而等待的所有线程。
sufficientFunds.signalAll();
可以创建多个不同的 Condition,实现不同的等待队列
Condition producer = lock.newCondition();
Condition comsumer = lock.newCondition();
while (isProducer) {
comsumer.await();
}
producer.signalAll();
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
/**
* 对所有的获取方法加读锁
*/
public doule getTotalBalance() {
readLock.lock();
try {
//...
} finally {
readLock.unlock();
}
}
/**
* 对所有的修改方法加写锁
*/
public void transfer() {
writeLock.lock();
try {
//...
} finally {
writeLock.unlock();
}
}
深入解析 volatile 、CAS 的实现原理
面试必问的 CAS,你懂了吗?
《面试必备之乐观锁与悲观锁》
并发策略:
乐观并发策略(乐观锁): 乐观地认为本次操作没有其他线程竞争。
先尝试进行操作,如果没有其它线程争用共享数据,那操作就成功了。否则采取补偿措施(不断地重试)直到成功为止。
悲观并发策略(悲观锁): 悲观地认为本次操作有其他线程竞争。
直接使用互斥量进行加锁阻塞。
原理: CAS 指令是原子操作,有 3 个操作数。分别是内存地址 V、旧的预期值 A 和新值 B。
当执行操作时,只有当 V 的值等于 A(即认为未被其他线程修改过),才将 V 的值更新为 B。
若失败了则重新获取这三个值再次进行判断,直到操作成功。
乐观锁适用于多读的应用类型,这样可以提高吞吐量
如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。
解决: J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。
改用传统的互斥同步可能会比原子类更高效。
LongAdder:
思想:分段锁 CAS,分片执行,结果汇总
例:1000 个线程分为 4 个任务,每个任务 250 个线程,执行完后汇总
J.U.C 包里面的 AtomicInteger 等原子类的方法调用了 Unsafe 类的 CAS 操作。
AtomicInteger:
内部调用 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
本地方法,compareAndSwapInt 定义在 jdk8u: unsafe.cpp 中
// AtomicInteger.class
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
compareAndSwapInt 调用 cmpxchg(compare and exchange)方法
// jdk8u: unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
cmpxchg 方法执行逻辑
以下是 cmpxchg 在 JDK 8,Linux 操作系统,X86 处理器环境下的实现:
// jdk8u: atomic_linux_x86.inline.hpp
inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
其详细执行逻辑如下:
is_MP(Multi Processor)方法判断 是否为多个处理器,保存到变量 mp 中。
// jdk8u: os.hpp
static inline bool is_MP() {
return (_processor_count != 1) || AssumeMP;
}
LOCK_IF_MP
// jdk8u: atomic_linux_x86.inline.hpp
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
如果是在多处理器上运行就为 cmpxchg 指令加上 lock
前缀(lock cmpxchg)。这就使 cmpxchg 指令变成原子操作
如果是在单处理器上运行就省略 lock
前缀
执行 cmpxchgl:比较并交换,操作成功返回比较值(旧值),操作失败返回目标地址中的值
“cmpxchgl %1,(%3)”
- “=a” (exchange_value)
- “r” (exchange_value), “a” (compare_value), “r” (dest), “r” (mp)
- “cc”, “memory”
输入 exchange_value(交换值,即更新值,%1)、compare_value(比较值,即期待值,%2)、dest(目标地址值,%3)、mp(是否多核,%4) 四个值
输出 exchange_value(%0)
cmpxchgl %1,(%3)
即表示 cmpxchgl exchange_value,(dest)
"r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
,代表把 compare_value 存入 eax 寄存器,把 exchange_value、dest、mp 的值存入任意的通用寄存器"=a" (exchange_value)
,把 eax 中存的值写入 exchange_value 变量中。return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
会返回 ture
,即表示 CAS 成功!否则表示 CAS失败。最终实现 cmpxchg = cas 修改变量值
lock 指令在执行后面指令的时候锁定一个北桥信号(不采用锁总线的方式)
lock cmpxchg 指令
面试必备之深入理解自旋锁
自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态
缺点: 如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗 CPU。使用不当会造成 CPU 使用率极高。
非公平不可重入:
当第一个线程 A 获取锁的时候,能够成功获取到,不会进入 while 循环,如果此时线程 A 没有释放锁,另一个线程 B 又来获取锁,此时由于不满足 CAS,所以就会进入 while 循环,不断判断是否满足 CAS,直到 A 线程调用 unlock 方法释放了该锁。
public class SpinLock {
private AtomicReference<Thread> cas = new AtomicReference<Thread>();
public void lock() {
Thread current = Thread.currentThread();
// 利用 CAS
while (!cas.compareAndSet(null, current)) {
// DO nothing
}
}
public void unlock() {
Thread current = Thread.currentThread();
cas.compareAndSet(current, null);
}
}
为了实现可重入锁,我们需要引入一个计数器,用来记录获取锁的线程数。
public class ReentrantSpinLock {
private AtomicReference<Thread> cas = new AtomicReference<Thread>();
private int count;
public void lock() {
Thread current = Thread.currentThread();
if (current == cas.get()) { // 如果当前线程已经获取到了锁,线程数增加一,然后返回
count++;
return;
}
// 如果没获取到锁,则通过CAS自旋
while (!cas.compareAndSet(null, current)) {
// DO nothing
}
}
public void unlock() {
Thread cur = Thread.currentThread();
if (cur == cas.get()) {
if (count > 0) {
// 如果大于0,表示当前线程多次获取了该锁,释放锁通过count减一来模拟
count--;
} else {
// 如果count==0,可以将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。
cas.compareAndSet(cur, null);
}
}
}
}
TicketLock 主要解决的是公平性的问题
public class TicketLock {
/**
* 服务号
*/
private AtomicInteger serviceNum = new AtomicInteger();
/**
* 排队号
*/
private AtomicInteger ticketNum = new AtomicInteger();
/**
* lock:获取锁,如果获取成功,返回当前线程的排队号,获取排队号用于释放锁.
*/
public int lock() {
int currentTicketNum = ticketNum.incrementAndGet();
while (currentTicketNum != serviceNum.get()) {
// Do nothing
}
return currentTicketNum;
}
/**
* unlock:释放锁,传入当前持有锁的线程的排队号
*/
public void unlock(int ticketnum) {
serviceNum.compareAndSet(ticketnum, ticketnum + 1);
}
}
上面的实现方式是,线程获取锁之后,将它的排队号返回,等该线程释放锁的时候,需要将该排队号传入。但这样是有风险的,因为这个排队号是可以被修改的,一旦排队号被不小心修改了,那么锁将不能被正确释放。一种更好的实现方式如下:
缺点:
多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量 serviceNum ,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能
public class TicketLockV2 {
/**
* 服务号
*/
private AtomicInteger serviceNum = new AtomicInteger();
/**
* 排队号
*/
private AtomicInteger ticketNum = new AtomicInteger();
/**
* 新增一个ThreadLocal,用于存储每个线程的排队号
*/
private ThreadLocal<Integer> ticketNumHolder = new ThreadLocal<Integer>();
public void lock() {
int currentTicketNum = ticketNum.incrementAndGet();
// 获取锁的时候,将当前线程的排队号保存起来
ticketNumHolder.set(currentTicketNum);
while (currentTicketNum != serviceNum.get()) {
// Do nothing
}
}
public void unlock() {
// 释放锁,从ThreadLocal中获取当前线程的排队号
Integer currentTickNum = ticketNumHolder.get();
serviceNum.compareAndSet(currentTickNum, currentTickNum + 1);
}
}
CLH 锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋,获得锁。
/**
* CLH的发明人是:Craig,Landin and Hagersten。
* 代码来源:http://ifeve.com/java_lock_see2/
*/
public class CLHLock {
/**
* 定义一个节点,默认的lock状态为true
*/
public static class CLHNode {
private volatile boolean isLocked = true;
}
/**
* 尾部节点,只用一个节点即可
*/
private volatile CLHNode tail;
private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();
private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER =
AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, CLHNode.class, "tail");
public void lock() {
// 新建节点并将节点与当前线程保存起来
CLHNode node = new CLHNode();
LOCAL.set(node);
// 将新建的节点设置为尾部节点,并返回旧的节点(原子操作)
// 这里旧的节点实际上就是当前节点的前驱节点
CLHNode preNode = UPDATER.getAndSet(this, node);
if (preNode != null) {
// 前驱节点不为null表示当锁被其他线程占用
// 通过不断轮询判断前驱节点的锁标志位等待前驱节点释放锁
while (preNode.isLocked) {
}
preNode = null;
LOCAL.set(node);
}
// 如果不存在前驱节点,表示该锁没有被其他线程占用,则当前线程获得锁
}
public void unlock() {
// 获取当前线程对应的节点
CLHNode node = LOCAL.get();
// 如果tail节点等于node,则将tail节点更新为null,同时将node的lock状态置为false,表示当前线程释放了锁
if (!UPDATER.compareAndSet(this, node, null)) {
node.isLocked = false;
}
node = null;
}
}
MCSLock 则是对本地变量的节点进行循环。
/**
* MCS:发明人名字John Mellor-Crummey和Michael Scott
* 代码来源:http://ifeve.com/java_lock_see2/
*/
public class MCSLock {
/**
* 节点,记录当前节点的锁状态以及后驱节点
*/
public static class MCSNode {
volatile MCSNode next;
volatile boolean isLocked = true;
}
private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>();
// 队列
@SuppressWarnings("unused")
private volatile MCSNode queue;
// queue更新器
private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER =
AtomicReferenceFieldUpdater.newUpdater(MCSLock.class, MCSNode.class, "queue");
public void lock() {
// 创建节点并保存到 ThreadLocal 中
MCSNode currentNode = new MCSNode();
NODE.set(currentNode);
// 将queue设置为当前节点,并且返回之前的节点
MCSNode preNode = UPDATER.getAndSet(this, currentNode);
if (preNode != null) {
// 如果之前节点不为null,表示锁已经被其他线程持有
preNode.next = currentNode;
// 循环判断,直到当前节点的锁标志位为false
while (currentNode.isLocked) {
}
}
}
public void unlock() {
MCSNode currentNode = NODE.get();
// next为null表示没有正在等待获取锁的线程
if (currentNode.next == null) {
// 更新状态并设置queue为null
if (UPDATER.compareAndSet(this, currentNode, null)) {
// 如果成功了,表示queue==currentNode,即当前节点后面没有节点了
return;
} else {
// 如果不成功,表示queue!=currentNode,即当前节点后面多了一个节点,表示有线程在等待
// 如果当前节点的后续节点为null,则需要等待其不为null(参考加锁方法)
while (currentNode.next == null) {
}
}
} else {
// 如果不为null,表示有线程在等待获取锁,此时将等待线程对应的节点锁状态更新为false,同时将当前线程的后继节点设为null
currentNode.next.isLocked = false;
currentNode.next = null;
}
}
}
深入解析 volatile 、CAS 的实现原理
处理器如何实现原子操作
处理器提供 总线锁定 和 缓存锁 定两个机制来保证复杂内存操作的原子性。
使用总线锁保证原子性
所谓总线锁就是使用处理器提供的一个 LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。
使用缓存锁保证原子性
底层用 lock 实现,如果是多核添加 lock 指令。
lock 用于在多处理器中执行指令时对共享内存的独占使用。
它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效
另外还提供了有序的指令无法越过这个内存屏障的作用
volatile 变量自身具有以下特性:
想了解以上特性的原理,需先了解 [处理器缓存](#0. 处理器缓存)。
CPU 缓存(Cache Memory)是位于 CPU 与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。
L1(一级缓存)分为 数据缓存 和 指令缓存,L2(二级缓存)和 L3(三级缓存)只有 数据缓存
作用:
缓存的出现主要是为了解决 CPU 运算速度与内存读写速度不匹配的矛盾,因为 CPU 运算速度要比内存读写速度快很多,这样会使 CPU 花费很长时间等待数据到来或把数据写入内存。
读缓存: CPU 依次从一级缓存、二级缓存、三级缓存中获取数据,若未命中则到内存中获取,再去更新缓存
缓存系统中是以缓存行(cache line)为单位存储的。缓存行是 2 的整数幂个连续字节,一般为 32-256 个字节。最常见的缓存行大小是 64 个字节。
因此当 CPU 在执行一条读内存指令时,它是会将内存地址所在的缓存行大小的内容都加载进缓存中的。也就是说,一次加载一整个缓存行。
写操作: 两种模式
直写(write-through):**更新内存数据再更新缓存(或丢弃)。**保证该数据在缓存与内存中一致
透过本级缓存,直接把数据写到下一级缓存(或直接到内存)中。
如果对应的段被缓存了,会同时更新缓存中的内容(甚至直接丢弃)。
回写(write-back):**先更新缓存,再由缓存回写至内存。**缓存暂时与内存不一致,但最终会写回内存。
仅修改本级缓存中的数据,并且把对应的缓存段标记为 “脏” 段。
脏段会触发回写,也就是把里面的内容写到对应的内存或下一级缓存中。
对一个 volatile 变量的读,总是能看到 (任意线程) 对这个 volatile 变量最后的写入。
在多核处理器系统中,每个处理器核心都有它们自己的一级缓存、二级缓存等。
这样一来当多个处理器核心在对共享的数据进行写操作时,就需要 保证该缓存数据在所有处理器核心中的可见性 / 一致性。
所有内存传输都发生在一条共享的总线上,而所有的处理器都能看到这条总线。
MESI 是缓存行四种状态的首字母缩写,任何多核系统中的缓存行都处于这四种状态之一。
失效(Invalid): 该处理器缓存中无该缓存行,或缓存中的缓存行已经失效了。
共享(Shared): 多组缓存都可以拥有指向同一内存地址的缓存行。且缓存行只能被读取,不能被写入。
该状态下缓存行数据是主内存的一份拷贝,其数据与主内存数据保持一致。
独占(Exclusive): 如果一个处理器持有了某个「独占」状态的缓存行,其他处理器中的同一缓存行会变成「失效」状态。
缓存行数据是主内存的一份拷贝,其数据与主内存数据保持一致。
已修改(Modified): 属于脏段,表示该缓存行已经被所属的处理器修改了。如果一个缓存行处于「已修改」状态,那么它在其他处理器缓存中的拷贝马上会变成「失效」状态。
已修改缓存行如果被丢弃或标记为「失效」状态,那么先要把它的内容回写到内存中,即需保证已经修改的数据一定要回写至内存。
写操作过程:
只有当缓存行处于「独占」状态或「已修改」状态时处理器才能对其进行写操作
当处理器想对某个缓存段进行写操作时,如果它没有独占权
读操作过程:
当处理器想对某个缓存段进行读操作时
若缓存行处于「独占」状态或「已修改」状态时,直接读
若其他处理器中有同一缓存行的拷贝且处于「独占」状态或「已修改」状态时(由于窥探总线技术,所以也会知道)需把状态改为「共享」状态才能进行读操作
其他处理器中的缓存行若为「已修改」状态需先把它的内容回写到内存中
操作系统通过内存屏障保证缓存间的可见性,JVM 通过给 volatile 变量加入内存屏障保证线程之间的可见性。
其实,volatile 对于可见性的实现,内存屏障也起着至关重要的作用。因为内存屏障相当于一个数据同步点,他要保证在这个同步点之后的读写操作必须在这个点之前的读写操作都执行完之后才可以执行。并且在遇到内存屏障的时候,缓存数据会和主存进行同步,或者把缓存数据写入主存、或者从主存把数据读取到缓存。
已经有了缓存一致性协议,为什么还需要 volatile?
并不是所有的硬件架构都提供了相同的一致性保证,Java 作为一门跨平台语言,JVM 需要提供一个统一的语义。
操作系统中的缓存和 JVM 中线程的本地内存并不是一回事,通常我们可以认为:MESI 可以解决缓存层面的可见性问题。使用 volatile 关键字,可以解决 JVM 层面的可见性问题。
缓存可见性问题的延伸:由于传统的 MESI 协议的执行成本比较大。所以 CPU 通过 Store Buffer 和 Invalidate Queue 组件来解决,但是由于这两个组件的引入,也导致缓存和主存之间的通信并不是实时的。也就是说,缓存一致性模型只能保证缓存变更可以保证其他缓存也跟着改变,但是不能保证立刻、马上执行。
DCL 单例:
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized(Singleton.class) {
if (singleton == null) {
// 若 singleton 没有加 volatile 会出现指令重排问题
singleton = new Singleton();
}
}
}
return singleton;
}
}
创建对象步骤: T t = new T();
对应指令
0 new #2
3 dup
4 invokespecial #3 >
7 astore_1
8 return
由于指令重排,2 和 3 可能会互换位置。这时变量可能会先拿到一个尚未初始化成员变量的对象,若刚好此时有线程进入 DCL 会直接拿到该变量去使用
读写屏障
概述: 用来控制一个线程等待多个线程
原理:
维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒
API:
实例:
public class CountdownLatchExample {
public static void main(String[] args) throws InterruptedException {
final int totalThread = 10;
CountDownLatch countDownLatch = new CountDownLatch(totalThread);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalThread; i++) {
executorService.execute(() -> {
System.out.print("run..");
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println("end");
executorService.shutdown();
}
}
// run..run..run..run..run..run..run..run..run..run..end
场景:
启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行
实现多个线程开始执行任务的最大并行性
CountDownLatch(1),多个线程挂起,当主线程调用 countDown() 时,多个线程同时被唤醒
不足:
CountDownLatch 是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当 CountDownLatch 使用完毕后,它不能再次被使用。
概述: 用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行
原理:
线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行
方法:
区别: CyclicBarrier 和 CountdownLatch 的区别是,CyclicBarrier 的计数器通过调用 reset() 方法可以 循环使用,所以它才叫做循环屏障
构造器: CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值,barrierAction 在所有线程都到达屏障的时候会选择一个线程执行一次
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
实例:
public class CyclicBarrierExample {
public static void main(String[] args) {
final int totalThread = 10;
CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalThread; i++) {
executorService.execute(() -> {
System.out.print("before..");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.print("after..");
});
}
executorService.shutdown();
}
}
// before..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after..
重用:
public class Test {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N);
for (int i = 0; i < N; i++) {
new Writer(barrier).start();
}
try {
Thread.sleep(25000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("CyclicBarrier重用");
for (int i = 0; i < N; i++) {
new Writer(barrier).start();
}
}
static class Writer extends Thread {
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "正在写入数据...");
try {
//以睡眠来模拟写入数据操作
Thread.sleep(5000);
System.out.println("线程" + Thread.currentThread().getName() + "写入数据完毕,等待其他线程写入完毕");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch(BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "所有线程写入完毕,继续处理其他任务...");
}
}
}
main:
public static void main(String[] args) {
phaser.bulkRegister(7);
for (int i = 0; i < 5; i++) {
new Thread(new Person("p" + i)).start();
}
new Thread(new Person("新郎")).start();
new Thread(new Person("新娘")).start();
}
Phaser:指定什么阶段做什么事
public ass MarriagePhaser extends Phaser {
// phase: 阶段编号: registeredParties: 此阶段注册人数
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase) {
case 0:
// ...
return false;
case 1:
// ...
return false;
case 2:
// ...
return false;
case 3:
// ...
return true;
default:
return true;
}
}
}
run:如何到达阶段逻辑
public class Person implements Runnable {
@Override
public void run() {
// doing something
// 等待所有注册的线程全部到达后执行
phaser.arriveAndAwaitAdvance();
if (name.equals("新郎") || name.equals("新娘")) {
System.out.printf("%s 洞房!\n", name);
phaser.arriveAndAwaitAdvance();
}
// 指定注销
else {
phaser.arriveAndDeregister();
//phaser.register()
}
}
}
StampedLock
ReadWriteLock readWriteLock = new ReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();
概述: Semaphore 类似于操作系统中的信号量,可以 控制对互斥资源的访问线程数
原理:
acquire() 获取一个许可,如果没有就等待
release() 释放一个许可
构造器:
API:
实例:
public class SemaphoreExample {
public static void main(String[] args) {
final int clientCount = 3;
final int totalRequestCount = 10;
Semaphore semaphore = new Semaphore(clientCount);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalRequestCount; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
System.out.print(semaphore.availablePermits() + " ");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
}
executorService.shutdown();
}
}
// 2 1 2 2 2 2 2 1 2 2
线程交换
Exchanger<String> exchanger = new Exchanger<>();
new Thread(() -> {
String s1 = "T1";
try {
// 阻塞等待交换,交换后才能继续执行
s1 = exchanger.exchange(s1);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + s1);
}, "t1").start();
new Thread(() -> {
String s2 = "T2";
try {
s2 = exchanger.exchange(s2);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + s2);
}, "t2").start();
// t1:T2
// t2:T1
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
// 停止当前线程
LockSupport.park();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
// 继续执行
LockSupport.unpark(t);
内部维护一个 state 和一个双向线程链表
AbstractQueuedSynchronizer#compareAndSetState:CAS 把 state 从 0 变为 1,若成功则代表拿到锁
AbstractOwnableSynchronizer#setExclusiveOwnerThread:若抢到锁,则设置当前线程为独占线程
AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
ReentrantLock.NonfairSync#tryAcquire
TryAcquire 失败则调用 AbstractQueuedSynchronizer#addWaiter:使用 CAS 加入链表队列
// jdk 8
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
// jdk 9 使用 VarHandler.set(this, pred)。
// VarHandler 内部有 CAS 的方法
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
jdk 9 使用 VarHandler.set(this, pred)
代替 node.prev = pred;
,其调用 native 实现(相当于直接操纵二进制码),效率比反射高
VarHandler 指向一个变量
// 指定某类下某名某类型的变量
VarHandler handle = MethodHandles.lookup().findVarHandle(TestClass.class, "test", int.class);
// 使用
TestClass testClass = new TestClass();
handle.set(testClass, 9); // 把该对象中的 test 属性变为 9
handle.compareAndSet(testClass, 9, 10); // 通过 CAS 更改值
handle.getAndAdd(testClass, 10); // 原子性添加值 => x+=10 的原子版本
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
管理多个线程运行后返回的结果
CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(() -> priceOfTM())
// 对结果进行处理
.thenApply(String::valueOf)
.thenApply(str -> "price " + str)
.thenAccept(System.out::println);
CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(() -> priceOfTB());
CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(() -> priceOfJD());
CompletableFuture.allOf(futureTM, futureTB, futureJD).join();
深度揭秘 ThreadLocal
把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题
使用:
public class ThreadLocalExample {
public static void main(String[] args) {
ThreadLocal threadLocal = new ThreadLocal();
Thread thread1 = new Thread(() -> {
threadLocal.set(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadLocal.get());
threadLocal.remove();
});
Thread thread2 = new Thread(() -> {
threadLocal.set(2);
threadLocal.remove();
});
thread1.start();
thread2.start();
}
}
// 1
原理:
ThreadLocal#set:把值存到当前线程的 ThreadLocalMap 中
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
// 线程中的 ThreadLocalMap 类型字段
return t.threadLocals;
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
内存泄露:
ThreadLocal 对象 new 后有强引用,而当前线程中的 ThreadLocalMap 对象的键也有 ThreadLocal 对象的弱引用,所以当 ThreadLocal 对象失去强引用时 ThreadLocalMap 中对应的键也会变为 null,防止了内存泄露。
虽然 ThreadLocalMap 的键为 null 了,但是其 value 值还存在所以依然会有内存泄露,所以需要执行 ThreadLocal#remove 方法。
线程在内核态
纤程在用户态,可以启动的数据比线程多