常用函数:
-
Wait : object的方法,所有类/对象都可以调用,调用wait方法的线程需要持有锁,通过notify,notifyAll唤醒,调用此方法的线程处于Waiting状态
java.lang.Thread.State: TIMED_WAITING (sleeping)
-
Sleep : Thread线程的方法,sleep一段时间后,自动唤醒继续执行,调用此方法的线程处于TIMED_WAITING状态
java.lang.Thread.State: TIMED_WAITING (sleeping)
Volatile
Volatile适用于多线程读,线程写不依赖于原始值的场景,具有如下特征:
- 与Synchronized相比,不加锁,较高的性能,
- 只能保证单词读/写的原子性
- 当前线程对值的修改,新值其他线程
立刻
可见
使用Volatile关键字修饰的变量的修改,将会立刻刷新至OS内存,鉴于缓存一致性协议,其他线程/处理器对该值的引用会立刻过期,当其他线程加载此值时,由于变量失效,将会从系统内存中重新读取,从而保证了读一致性
Synchronized
synchronized常用于多线程同步,线程进入同步代码块或方法时自动获得内置锁,退出同步代码块或方法时自动释放该内置锁。进入同步代码块或者同步方法是获得内置锁的唯一途径
synchronized关键字描述 锁类型 锁对象 实例synchronized方法 实例对象的内置锁 实例 类的synchronized静态方法 class内置锁 对象 类的synchronized代码块 内置代码块对象 代码块对象
可重入锁(ReentrantLock)
可重入锁同样是一种排它锁,使用之前需要先获取,使用完需要释放。释放的代码通常放置在finally代码块中:
class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } } }}
可重入锁的主要方法有:
方法 | 含义 | 备注 |
---|---|---|
lock | 获取锁 | 阻塞 |
lockInterruptibly | 尝试获取锁,如果线程被interrupt,则抛出异常 | 阻塞 |
tryLock | 尝试获取锁,获取到返回true,获取不到返回false | 非阻塞 |
tryLock(long time, TimeUnit unit) | 尝试在规定时间范围内获取锁,获取到返回true,否则返回false,如果被interrupt则抛出异常 | 阻塞 |
unlock | 释放锁 | 通常在finally代码块中 |
公平锁&非公平锁
锁类型 | 初始化方法 | 效果 |
---|---|---|
非公平锁 | 默认非公平锁,Lock lock = new RenentrantLock(false) | 多个线程申请获取锁时,随机选取其中一个候选线程获取锁 |
公平锁 | Lock lock = new RenentrantLock(true) | 多个线程申请获取锁时,最先申请锁的线程先获取锁 |
读写锁
读写锁中包含读锁和写锁,使用与多读少写的场景,对于同一个读写锁而言,有如下规则:
获取读锁后,其他线程扔可以获取读锁,而不能获取写锁
获取写锁后,其他线程既不能获取读锁,也不能获取血锁
条件锁
条件锁本身不存在,每一个实现AbstractQueuedSynchronized类的对象都可以生成若干个条件对象,由于可重入锁(后续以RenentrantLock为例分析)中包含了一个Sync,定义如下:
abstract static class Sync extends AbstractQueuedSynchronizer
在获取锁之后,线程可以通过条件对象的相关方法释放锁并实现不同的等待语义,主要方法如下:
方法 | 含义 | 备注 |
---|---|---|
await()/ | 获取锁之后可调用此方法暂时释放锁,并等待期对应的条件对象的唤醒,如果等待过程中被打断,则抛出异常,并返回 | await(Long,TimeUnit)<br />awaiNanos(Long)<br />awaitUntil(Date) |
awaitUninterruptibly() | 获取锁之后可调用此方法暂时释放锁,并等待期对应的条件对象的唤醒,如果等待过程中被打断,则保留异常,继续等待 | |
signal() | 用于唤醒条件对象,如果有多个线程调用改条件对象的await方法,则随机选取一个唤醒 | signalAll()会唤醒所有该条件对象的等待线程 |
调用上述任意条件等待方法的前提都是当前线程已经获得与该条件对象对应的重入锁。 调用条件等待后,当前线程让出CPU资源。 一旦条件等待方法返回,则当前线程肯定已经获得了对应的重入锁 重入锁可以创建若干个条件对象,*signal()*和*signalAll()*方法只能唤醒相同条件对象的等待 一个重入锁上可以生成多个条件变量,不同线程可以等待不同的条件,从而实现更加细粒度的的线程间通信
信号量
信号量Semaphore主要用于限制同一临界资源的同时访问个数。互斥锁限制的是同一时间只有一个线程可以访问临界资源,而信号量则用于限制同一时间有多个线程可以访问临界资源。
其工作模式是先生成一个有特定数目(就是最多可允许同时访问临界资源的线程数)许可证的信号量,然后当线程获取许可证时,如果能获取到,则会在总许可证中减去相应的许可证;当线程归还许可证时,则在总许可证中加上相应的许可证。
不同的方法可以实现不同的许可证申请语义,常用如下:
含义 | 备注 | |
---|---|---|
acquire() | 尝试获取1个许可证,如果获取过程中线程被打断,则抛出异常 | acquire(int),阻塞 |
acquireUninterruptibly() | 尝试获取1个许可证,如果获取过程中线程被打断,则保留异常,继续等待 | acquireUninterruptibly(int),阻塞 |
drainPermits() | 获取当前可用的许可数 | 非阻塞 |
tryAcquire() | 尝试获取许可 | tryAcquire(int),非阻塞 |
tryAcquire(long , TimeUnit ) | 尝试在一定时间内,获取许可,中间被打断,则抛出异常 | tryAcquire(int,long , TimeUnit ),阻塞 |
release() | 归还许可 | release(int) |
线程间通信
JVM在运行过程中,不同的线程执行不同的任务,有时不同任务之间的某些操作有顺序关系,如线程A需要B执性完某个操作后才可以继续执行,或者线程C需要等待线程D,E,F执行完后才能继续执行,这就需要通过某些
标志
来完成通信
, 如上述的条件锁,信号量等都可以作为线程间通信的标志
如下介绍其他可用于线程间通信的标志
CountDownLatch
CountDownLatch,翻译成中文的含义就是向下计数的阀门,用于一个线程的继续执行需要等待一个或多个线程执行至某个状态后开始的场景。比如当前有一个全场篮球场地,有两拨人分别在在不同半场打半场对抗赛,如果我们想去打全场比赛,则需要等两拨人的半场比赛都完毕之后才可以执行。
其功能实现主要包含一个Sync定义如下:
private static final class Sync extends AbstractQueuedSynchronizer {}
其初始化方法如下:
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
CountDownLatch的实现较为简单,主要有如下几个函数,均通过调用sync实现
方法 | 含义 | 备注 |
---|---|---|
countDown | 向下计数 | |
await() | 等待countDownLatch,直至count变为0,被打断将抛出异常 | 阻塞 |
await(long, TimeUnit ) | 等待直至指定时间,指定时间内返回,则返回true,否则false,被打断将抛出异常 | 阻塞 |
CyclicBarrier
CyclicBarrier,翻译成中文就是循环栅栏,用于等待所有的线程执行完毕后才继续执行后续的操作。如A、B两人参加运动会(分别参赛5Km长跑,跳高,铅球等项目),C是裁判,A,B分别完成5Km后,分别通知裁判自己讲开始等待(阻塞),等裁判看到A、B均完成比赛后(A、B均处于等待状态),则继续进行跳高(在跳高之前可能会先对A、B的成绩汇总统计给出名次),同样,当裁判确认A、B均完成跳高比赛后,则可以开始进行铅球项目的比赛。
该机制可以简单类比成狭义的分布式计算中的Map和Reduce计算,当所有的Map(线程/进程)都分别完成自己的计算之后,将计算结果输出,可以进入下一个阶段的Reduce计算。
其功能实现主要依赖可重入锁和条件对象:
/** The lock for guarding barrier entry */ private final ReentrantLock lock = new ReentrantLock(); /** Condition to wait on until tripped */ private final Condition trip = lock.newCondition();
其构造函数有:
public CyclicBarrier(int parties, Runnable barrierAction) {} public CyclicBarrier(int parties) { this(parties, null); }
其常用方法如下:
方法 | 含义 | 备注 |
---|---|---|
reset | 重置barrier | |
await() | 当一个线程运行至某一个阶段时,可通过此方法进入栅栏,等待下一步操作 | 阻塞 |
await(long, TimeUnit ) | 此方法进入栅栏,被打断或超时将抛出异常 | 阻塞 |
Phaser
Phase:中文翻译为阶段
,Phaser翻译为阶段器
。 Phaser是JDK1.7中新加入的线程并发辅助类,擅长于多个阶段并发操作的工作。使用CountDownLatch和CyclicBarrier的场景下,通常都可以使用Phaser完成。且Phaser的语义/功能更为强大,这也导致Phaser的实现相比CountDownLatch和CyclicBarrier复杂不少,相关API接口比较复杂。可查看相关源码。
如上述CyclicBarrier的例子,如果在不同的体育项目比赛中,分别允许其他队员的加入,或者A、B队员的退出,则使用CyclicBarrier就无法解决此场景,此时可以考虑Phaser.
ThreadLocal
ThreadLocal 适用于变量在线程之间隔离,但在方法之间共享的场景。并非用于多线程间解决数据共享问题。
ThreadLocal的出现并非解决了之前不能解决的问题,只是简化了某些场景的实现,使代码可读性大大增加。
其实现为每个线程维护自己的ThreadLocal实例,对其他线程不可见,从根本上屏蔽了线程同步。为防止内存泄露,其实现设计弱引用及其他设计思路,实现原理值得研究学习。
网友评论