ReentrantLock (重入锁)
可重入
单线程可以重复进入,但要重复退出
可中断
lockInterruptibly()
防止线程因为迟迟无法完成操作而占用大量时间。
可限时
超时不能获得锁,就返回false,不会永久等待构成死锁
公平锁
先来先得
public ReentrantLock(boolean fair)
public static ReentrantLock fairLock = new ReentrantLock(true);
示例1:
标准用法:
示例2:
可重复 连续两次加锁,但是也必须解锁两次,不然线程卡死
示例3:
可中断
第23行的 lock1.lockInterruptably(); 这个方法加锁, 表示这个锁可以中断,中断后就抛出异常
最后一行代码是个守护线程,专门检查死锁的实现代码如下:
可限时.tryLock()的使用:
运行结果会导致锁的申请失败。
重入锁的条件Condition
概述
类似于 Object.wait()和Object.notify() 与ReentrantLock结合使用
主要接口
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException; void signal();
void signalAll();
.API详解
await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线 程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。
awaitUninterruptibly()方法与await()方法基本相同,但是它并不会再等待过程中响应中断。
singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obejct.notify()方法很类似。
示例:
.awalt()线程挂起等待
.signal()唤醒线程继续执行, 必须重新获得锁才能执行。
Semaphore (信号量,共享锁)
概述 共享锁 运行多个线程同时临界区 ,
可以规定多少个许可之内允许线程进入,
一个线程有可以有一个许可,也可以有多个许可,跟业务逻辑相关
超出规定的许可将会跟锁一样,不允许线程的进入。
主要接口
//获得信号量
public void acquire()
//获得信号量,不支持中断
public void acquireUninterruptibly()
//尝试获取信号量
public boolean tryAcquire()
//限时尝试获取信号量
public boolean tryAcquire(long timeout, TimeUnit unit)
//释放信号量
public void release()
简单示例:
acquire()获取 ,release()释放
因为通行许可为5,这里有20个线程,每个线程延迟2秒,所以结果是每2秒完成一波,一共分四波完成
ReadWriteLock (读写锁)
概述
ReadWriteLock是JDK5中提供的读写分离锁 ,允许多个线程读操作,如果没有写的操作,所有读的 操作都是无等待的并发。
所有线程加锁会严重影响性能,这时候就可以使用分功能的读写分离锁了 。
访问情况
读-读不互斥:读读之间不阻塞。
读-写互斥:读阻塞写,写也会阻塞读。
写-写互斥:写写阻塞。
主要接口
//NEW对象
private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//获得读锁
private static Lock readLock = readWriteLock.readLock();
//获得写锁
private static Lock writeLock = readWriteLock.writeLock();
//加锁解锁
lock() unlock()
CountDownLatch (倒数计时器)
概述 倒数计时器 一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。 只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程 ,等待所有检查线程全部完工后,再执行
主要接口
//构造⏲记时的线程数
static final CountDownLatch end = new CountDownLatch(10);
//完成一个线程
end.countDown();
//开始执行主线程
end.await();
示意图
主线程到达临界点等待其它线程,等其它线程完成自己的工作后,主线程才继续执行。
示例:
CyclicBarrier (循环栅栏)
概述
循环栅栏 Cyclic意为循环,也就是说这个计数器可以反复使用。比如,假设我们将计数器设置为10。那么凑齐第一批1 0个线程后,计数器就会归零,然后接着凑齐下一批10个线程
主要接口
public CyclicBarrier(int parties, Runnable barrierAction)
barrierAction就是当计数器一次计数完成后,系统会执行的动作
await()
示意图
代码示例:
注:最后三行代码会中断一个线程的操作,所以会抛出异常。
LockSupport (线程阻塞工具类)
概述
提供线程阻塞原语
主要接口
//停止线程
LockSupport.park();
//指定线程继续执行
LockSupport.unpark(t1);
与suspend()比较
不容易引起线程冻结
中断响应
能够响应中断,但不抛出异常。
中断响应的结果是,park()函数的返回,可以从Thread.interrupted()得到中断标志
示例
线程被park()(挂起)了想要继续执行 ,要么unpark,要么被中断,且并不会抛出中断异常。
ReentrantLock 的实现
CAS状态
拿锁的机制就是通过cas算法来实现的,是实现重入锁的关键
等待队列
如果没拿到锁就会进入等待队列, 把所有等待的线程保存起来
park()
所有在等待的线程都是进行了park(挂起)的操作,当前面一个线程unlock(解锁)后才会在等待队列中挑一个进行unpark(解除挂起),然后 才进行加锁,任务完成后unlock解锁,如此循环往复。
并发容器及典型源码分析
集合包装
HashMap
Collections.synchronizedMap public static Map m=Collections.synchronizedMap(new HashMap());
这种方式只适合并发量比较小的情况,因为实现方式大量的用到了同步锁。
HashMap的构成:
List
synchronizedList
同上,用到同步锁
.Set
synchronizedSet
同上,用到同步锁
ConcurrentHashMap
高性能HashMap
在进行put操作的时候,会把HashMap分成若干个小的HashMap(Segment<K,V>),再进行操作,避免线程之间操作大的HashMap而引发冲突。
BlockingQueue
阻塞队列
线程安全,但是性能并不怎么好,在多线程环境下能起到共享数据的作用
线程进行读的操作,如果数据为空,就会进入等待 状态,等另一个线程往数据里写数据的之后才唤醒读的线程再进行读的操作。
如果数据满了,线程想进行写的操作,只有等其它线程拿掉数据后才能写。
这就是阻塞队列,会引起线程的阻塞
ConcurrentLinkedQueue
高性能链表队列,功能同上,性能更好,不会引起阻塞