概念
- 死锁
- 饥饿:例如CAS自旋一直失败
- 活锁:线程死锁之后,同时放弃锁,再同时获得部分的锁,导致程序依然不能顺利走下去
- 并发级别:
-
- 阻塞
- 非阻塞
-
- 无障碍
-
- 最弱的非阻塞
- 自由进入临界区
- 无竞争时,有限步内完成操作
- 有竞争,回滚数据
- 无锁(lock-free)
-
- 无障碍
- 保证有一个线程可以胜出(额外条件,保证线程不会全部失败)
- 无等待(wait-free)
-
- 无锁
- 所有线程有限步内完成
- 无饥饿
多线程及基础
- 线程的基本操作
-
- 新建线程:new Thread([Runnable]).start()
- 终止线程:
-
- thread.stop(),释放所有锁,立刻停止,可能导致数据的不一致
- 中断方式
-
- public void Thread.interrupt() // 中断线程
- public boolean Thread.isInterrupted() // 判断是否被中断
- public static boolean Thread.interrupted() // 判断是否被中断,并清除当前中断状态
-
public void run(){while(true){if(Thread.currentThread().isInterrupted()){ //在一些可以存档的点判断中断状态并退出System.out.println("Interruted!");break;}doSomething();}}
-
public void run(){while(true){if(Thread.currentThread().isInterrupted()){System.out.println("Interruted!");break;}try {Thread.sleep(2000);} catch (InterruptedException e) {//抛出异常后,线程的中断状态已被清空System.out.println("Interruted When Sleep");//设置中断状态Thread.currentThread().interrupt();}doSomething();}}
- 挂起线程
-
- thread.suspend()
- 不会释放锁,如果不继续执行(resume),会导致死锁
- 恢复线程
-
- thread.resume()
- 对应thread.suspend()
- 等待线程
-
- thread.join()
-
public final void join() throws InterruptedExceptionpublic final synchronized void join(long millis) throws InterruptedException
public class JoinMain {public volatile static int i=0;public static class AddThread extends Thread{@Overridepublic void run() {for(i=0;i<10000000;i++); }}public static void main(String[] args) throws InterruptedException {AddThread at=new AddThread();at.start();at.join();System.out.println(i);}} - 本质上是wait进行实现
-
-
public final synchronized void join(long millis)throws InterruptedException {long base = System.currentTimeMillis();long now = 0;
if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}
if (millis == 0) {while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}} - 线程执行完之后会调用lockObj.notifyAll()
-
- 线程放手
-
- thread.yield()
- Yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。
- Yield不能保证使得当前正在运行的线程迅速转换到可运行的状态
- 它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态
- 守护线程
-
- thread.setDaemon(true) //需要在thread.start()之前设定
- 当一个Java应用内,只有守护线程时,Java虚拟机就会自然退出
- 线程优先级
-
- thread.setPriority() //需要在thread.start()之前设定
- 高优先级的线程更容易再竞争中获胜(更容易获取锁及相应的monitor)
- 默认NORM_PRIORITY = 5;最小MIN_PRIORITY = 1;最大MAX_PRIORITY = 10;
- 基本的线程同步操作
-
- synchronized,注意其加在static方法上时,相应的lockObj是当前的class对象
- object.wait,object.notify(),object.notifyAll()
-
-
public static class T2 extends Thread {public void run() {synchronized (object) {System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread");object.notify();System.out.println(System.currentTimeMillis() + ":T2 end!");try {Thread.sleep(2000);} catch (InterruptedException e) {}}}}
public static class T1 extends Thread {public void run() {synchronized (object) {System.out.println(System.currentTimeMillis() + ":T1 start! ");try {System.out.println(System.currentTimeMillis() + ":T1 wait for object ");object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(System.currentTimeMillis() + ":T1 end!");}}} - 必须占用object的monitor之后(object是sychronized住的对象,且在同步代码内),才能调用相应的notify,notifyAll,wait方法
- wait之后会立即释放锁,但是notify,notifyAll并不会,只有等退出同步代码块,锁才会被释放
- thread.interrupt包含同步代码,所有wait状态的线程先获得锁,将跳出try代码块,就不会有中断异常抛出
-
-
public class App {
private static final Object lock = new Object();
private static class T1 extends Thread {public void run() {System.out.println("t1 priority: "+ this.getPriority());synchronized (lock) {try {lock.wait();} catch (InterruptedException e) { // t2释放锁,每次都是t1先获取(不清楚原因),所已main的t1.interrupt不会导致t1抛出异常System.out.println(new Date().getTime() + " t1 interrupted");System.out.println("t1 isInterrupted: " + this.isInterrupted());}}}}
private static class T2 extends Thread {public void run() {synchronized (lock) {lock.notify();System.out.println(new Date().getTime() + " t2 notify");try {Thread.sleep(5000L);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(new Date().getTime() + " t2 end");}}}
public static void main(String[] args) throws InterruptedException {System.out.println("main priority: "+ Thread.currentThread().getPriority());T1 t1 = new T1();t1.start();new T2().start();Thread.sleep(1000L);t1.interrupt();}}
-
-
Java 内存模型及线程安全
- 原子性
- 有序性:
-
- 程序可能出现乱序,一条指令的执行是可以分为很多步骤的
- 取指 IF
- 译码和取寄存器操作数 ID
- 执行或者有效地址计算 EX
- 存储器访问 MEM
- 写回 WB
- 可见性
-
-
- 编译器优化:处理器是以流水线进行执行的,为了减少空指令(如下图中的x)编译器会对指令进行重排,CPU也会进行重排
- 硬件优化:
-
- 写吸收:对一个变量重复的写操作只执行最后一个写入内存(写操作在硬件中排队,类似redis的AOF重写)
- example:
-
- while(!stop); 在子线程中运行,main 线程中将stop 从 false 置为 true,以-server 模式启动,while 循环始终不能退出,原因:子线程中的stop始终缓存在寄存器中,不会去读取内存中的stop
-
- happen-before
-
- volatile变量的写(写内存),先发生于读,保证了可见性
- 线程的 中断先于被中断的代码
无锁
- CAS(compare and set/compare and sweep),本质上是带条件的写操作
-
- 参数:variable V, expected E, new N
- 无锁类的使用
-
- AtomicInteger
-
- 实现 Number 接口,Java 所有数值类均实现这个接口
- 主要接口 //涉及到之前状态,保证线程安全
-
- compareAndSet
- getAndIncrement
- getAndDecrement
- getAndAdd
- incrementAndGet
- decrementAndGet
- addAndGet
- 主要接口实现
-
- 使用Unsafe实现
- Unsafe
-
- 概述
-
- 不安全的操作
-
- 根据偏移量设置值(指针)
- park 线程挂起
- 底层CAS操作
- 非公开API,不能保证向前兼容
- 主要接口(均是jndi实现,不可见)
-
-
- //获得给定对象偏移量上的int值
- public native int getInt(Object o, long offset);
-
- //设置给定对象偏移量上的int值
- public native void putInt(Object o, long offset, int x);
-
- //获得字段在对象中的偏移量
- public native long objectFieldOffset(Field f);
-
- //设置给定对象的int值,使用volatile语义
- public native void putIntVolatile(Object o, long offset, int x);
-
- //获得给定对象对象的int值,使用volatile语义
- public native int getIntVolatile(Object o, long offset);
-
- //和putIntVolatile()一样,但是它要求被操作字段就是volatile类型的
- public native void putOrderedInt(Object o, long offset, int x);
-
- AtomicReference
-
- 概述
-
- 修改引用(32bit JDK为32位。64bit JDK 默认会对 常规引用压缩到32bit,通过JVM参数设置)
- 使用泛型
- AtomicStampedReference
-
- 在AtomicReference的基础上解决 ABA 的问题
- AtomicIntegerArray
-
- 支持无锁的数组
- 接口:
-
-
- //获得数组第i个下标的元素
- public final int get(int i)
-
- //获得数组的长度
- public final int length()
-
- //将数组第i个下标设置为newValue,并返回旧的值
- public final int getAndSet(int i, int newValue)
-
- //进行CAS操作,如果第i个下标的元素等于expect,则设置为update,设置成功返回true
- public final boolean compareAndSet(int i, int expect, int update)
-
- //将第i个下标的元素加1
- public final int getAndIncrement(int i)
-
- //将第i个下标的元素减1
- public final int getAndDecrement(int i)
-
- //将第i个下标的元素增加delta(delta可以是负数)
- public final int getAndAdd(int i, int delta)
-
- AtomicIntegerFieldUpdater
-
- 让普通int变量也享受原子操作
- 接口:
-
- AtomicIntegerFieldUpdater.newUpdater()
- incrementAndGet()
- 小说明
-
- Updater只能修改它可见范围内的变量。因为Updater使用反射得到这个变量。如果变量不可见,就会出错。比如如果score申明为private,就是不可行的。
- 为了确保变量被正确的读取,它必须是volatile类型的。如果我们原有代码中未申明这个类型,那么简单得申明一下就行,这不会引起什么问题。
- 由于CAS操作会通过对象实例中的偏移量直接进行赋值,因此,它不支持static字段(Unsafe.objectFieldOffset()不支持静态变量)。
- 由于JDK concurrent 包提供的并发集合类有限(只有 ConcurrentHashMap, ConcurrentLinkedQueue 等),第三方jar包实现了类似的CAS并发集合类的实现(例如Amino),源码包如下:
同步控制工具(CAS 无法实现同步控制)
- ReentrantLock
-
- 可重入:单线程可以重复进入,但是要重复退出
- 可中断:lockInterruptibly()
- 可限时:和可中断一样,可以用来防止死锁
- 公平锁:先来先得
-
- public ReentrantLock(boolean fair)
- public static ReentrantLock fairLock = new ReentrantLock(true);
- 性能比非公平查,据说差的比较多
- Condition
-
- 类似 wait,notify
- 从ReentrantLock 获取
- 接口:
-
- void await() throws InterruptedException; //会释放相关的ReentrantLock
-
static class T4 extends Thread {@Overridepublic void run() {try {lock.lock();try {condition.await();} catch (InterruptedException e) {System.out.println(Thread.currentThread().isInterrupted());System.out.println(new Date().getTime());}}finally {lock.unlock();}}}
private static Condition condition;private static ReentrantLock lock;
public static void main(String[] args) throws InterruptedException {lock = new ReentrantLock();condition = lock.newCondition();T4 t4 = new T4();t4.start();Thread.sleep(100);try {lock.lock();t4.interrupt();System.out.println(new Date().getTime());Thread.sleep(5000);}finally {lock.unlock();}}
Console:1492350704145false1492350709146 - 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();
- Semaphore
-
- 共享锁,多个线程同时临界区,指定许可个数,获得许可的线程可以同时进入同步代码块
- 接口:
-
- public void acquire()
- public void acquireUninterruptibly()
- public boolean tryAcquire()
- public boolean tryAcquire(long timeout, TimeUnit unit)
- public void release()
- ReadWriteLock
-
- 接口:
-
- private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
- private static Lock readLock = readWriteLock.readLock();
- private static Lock writeLock = readWriteLock.writeLock();
- CountDownLatch
-
- 接口:
-
- countDown
- await
- CyclicBarrier
-
-
private static final CyclicBarrier CYCLIC_BARRIER = new CyclicBarrier(2, new Runnable() {public void run() {System.out.println(Thread.currentThread().getId() + " action " + new Date());}});
static class CyclicBarrierThread implements Runnable {
private CyclicBarrier cyclicBarrier;
public CyclicBarrierThread(CyclicBarrier cyclicBarrier) {this.cyclicBarrier = cyclicBarrier;}
public void run() {System.out.println(Thread.currentThread().getId() + " start " + new Date());try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getId() + " start " + new Date());}}
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {new Thread(new CyclicBarrierThread(CYCLIC_BARRIER)).start();System.out.println(Thread.currentThread().getId() + " main start " + new Date());CYCLIC_BARRIER.await();System.out.println(Thread.currentThread().getId() + " main end " + new Date());
} -
1 main start Thu Apr 20 23:18:09 CST 201712 start Thu Apr 20 23:18:09 CST 201712 action Thu Apr 20 23:18:09 CST 201712 start Thu Apr 20 23:18:09 CST 20171 main end Thu Apr 20 23:18:09 CST 2017
- 接口:
-
- public CyclicBarrier(int parties, Runnable barrierAction)
-
- //barrierAction就是当计数器一次计数完成后,最后一个线程会执行的动作
- await
-
- LockSupport
-
- 线程阻塞
- 接口:
-
- park(调用Unsafe的park实现)
- unpark
- 和suspend比较:不易引起线程冻结(resume发生在suspend之前会导致线程冻结),其使用许可证(permission 默认被占用),unpark 取消 permission 的占用,park 占用 permission,获取不到,则线程挂起,也就是说:unpark -> park,线程不会挂起
- 线程挂起是比较底层的操作,sychronized 默认是先CAS,偏向锁,最后才会挂起等待,线程的挂起操作比较占用资源
- 如果线程处于LockSupport.park(挂起阻塞)的时候被中断,则park方法返回,且中断isInterrupted返回true
- ReentrantLock
-
- lock的状态通过CAS保证并发
- 支持公平模式,公平模式下,等待线程进入队列
- 线程lock失败则自旋重复尝试,多次失败后,调用pack挂起
- ConcurrentHashMap
-
- CAS并发实现的hashmap,并发性能强
- jdk8 对ConcurrentHashMap重新实现,差别如下
-
- 原来的size方法有同步,jdk8改为无同步
- 对hash值相同的原来是链表的数据结构改为红黑树,在loadFactor较大的情况下,性能有较大提高
- BlockingQueue
-
- 阻塞队列
- ConcurrentLinkedQueue
-
- 并发队列
线程池
- 线程池的使用
-
- 种类(Executors static method)
-
- newFixedThreadPool:固定数量
- newSingleThreadExecutor:单个线程
- newCachedThreadPool:自由收缩线程池(core size, max size)
- newScheduledThreadPool:定时线程池
- ThreadPoolExecutor constructor
-
-
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);scheduledThreadPool.scheduleWithFixedDelay(new Runnable() {public void run() {try {Thread.currentThread().sleep(1000);System.out.println(new Date().getTime() / 1000);System.out.println(Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}, 1, 2, TimeUnit.SECONDS);System.out.println("main end");}
Console:main end1494340133pool-1-thread-11494340136pool-1-thread-11494340139pool-1-thread-21494340142pool-1-thread-1 - ThreadPoolExecutor 提供了扩展接口:
-
- beforeExecute
- afterExecute
- terminated
-
- 拒绝策略
-
- abort:抛弃任务,抛出异常,打印任务信息(默认策略)
- discard:静静丢弃掉
- callerRuns:调用者去做
- discardOldest:丢弃最老的请求
- 拒绝策略在向线程池的Queue中放任务(execute/submit)失败的时候生效
- 当前线程都在跑的时候,加入新的任务,会先试图将任务加入Queue,queue放不下,则试图new新的线程执行,如果线程到达max size上线,则调用拒绝策略
- 线程工厂:ThreadFactory
-
public Thread newThread(Runnable r) {Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0);if (t.isDaemon())t.setDaemon(false);if (t.getPriority() != Thread.NORM_PRIORITY)t.setPriority(Thread.NORM_PRIORITY);return t;}
- 线程异常情况
-
- 线程池中的线程默认是不能被中断,线程池shutdowmNow是通过信号量终结线程
- 线程池运行时抛出异常,异常在run方法中会被捕获并吃掉,不会影响到execute层的代码,对再次执行任务没有影响
并发模式
- Future
-
-
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);FutureTask<String> stringFutureTask = new FutureTask<String>(new Callable<String>() {public String call() throws Exception {Thread.currentThread().sleep(5000);return "hello";}});Future<?> res = scheduledThreadPool.submit(stringFutureTask);Thread.currentThread().sleep(2500);System.out.println(new Date().getTime());String resObj = stringFutureTask.get();//阻塞System.out.println(resObj);System.out.println(new Date().getTime()-2500);
Console:1494344441716hello1494344441716 - 另起一个线程去执行,立刻返回的是实际结果的一个装饰类,调用装饰类获取实际的返回结果时,如果实际返回结果还没有被set,则挂起线程等待
-
NIO & AIO
- nio 的使用
-
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);AbstractSelector selector = SelectorProvider.provider().openSelector();SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);key.interestOps(SelectionKey.OP_ACCEPT | SelectionKey.OP_READ); //注册之后可以修改感兴趣的时间
for (;;){selector.select();Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()){SelectionKey k = iterator.next();iterator.remove();if (k.isValid() && k.isAcceptable()){ // invalid: key cancelled, connection disconnect, selector shutdownServerSocketChannel channel = (ServerSocketChannel)k.channel();SocketChannel socketChannel = channel.accept();socketChannel.configureBlocking(false);SelectionKey register = socketChannel.register(selector, SelectionKey.OP_READ);register.attach(new Object());//将数据放入register.attachment();//取出数据}}}
锁优化
- 思路和方法
-
- 减少锁持有时间:只有在需要的时候才持有锁
- 减小锁粒度:将需要同步的数据进行拆分,降低竞争的可能
- 锁分离:读写锁
- 锁粗化:如果for循环体的代码需要锁,在for外层加锁,而不是在里面加锁,因为获取锁也是要成本的
- 锁消除:
-
- 无锁的方式:CAS,因为线程的挂起需要8万个CPU时钟周期
- 在变量不会逸出的时候,消除锁
-
- -server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
- JVM内部锁优化
-
- 偏向锁
-
- Mark Word:对象的头标记,32 bit
-
- 描述对象 的hash,锁信息,垃圾回收标记,年龄
-
- 指向锁记录的指针
- 指向monitor的指针
- GC标记
- 偏向锁线程ID
- 大部分情况是没有竞争的,所以可以通过偏向来提高性能
- 所谓的偏向,就是偏心,即锁会偏向于当前已经占有锁的线程
- 将对象头Mark的标记设置为偏向,并将线程ID写入对象头Mark
- 只要没有竞争,获得偏向锁的线程,在将来进入同步块,不需要做同步
- 当其他线程请求相同的锁时,偏向模式结束
- -XX:+UseBiasedLocking 默认启用
- 在竞争激烈的场合,偏向锁会增加系统负担
-
public static List<Integer> numberList = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {long begin = System.currentTimeMillis();int count = 0;int startnum = 0;while (count < 10000000) {numberList.add(startnum);startnum += 2;count++;}long end = System.currentTimeMillis();System.out.println(end - begin);} - -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0(系统起来的时候不会使用偏向锁,默认起来4000ms之后启用),在上面的例子中,快了约5%
- 轻量级锁 BasicLock
-
- 普通的锁处理性能不够理想,轻量级锁是一种快速的锁定方法(比操作系统级的互斥快很多)。
- 如果对象没有被锁定
-
- 将对象头的Mark指针保存到锁对象中
- 将对象头设置为指向锁(在线程栈空间中)的指针
- 判断一个线程有没有持有这把锁,判断对象头部指向的地址是否在栈空间中
- CAS操作
-
lock->set_displaced_header(mark);if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {TEVENT (slow_enter: release stacklock) ;return ;}
- 如果轻量级锁失败,表示存在竞争,升级为重量级锁(常规锁);但是轻量级锁失败,线程可能先进行一段时间的CAS自旋,自旋失败,再升级为重量级锁;
- 在没有锁竞争的前提下,减少传统锁使用OS互斥量产生的性能损耗
- 在竞争激烈时,轻量级锁会多做很多额外操作,导致性能下降
- 自旋锁
-
- 当竞争存在时,如果线程可以很快获得锁,那么可以不在OS层挂起线程,让线程做几个空操作(自旋)
- JDK1.6中-XX:+UseSpinning开启
- JDK1.7中,去掉此参数,改为内置实现
- 如果同步块很长,自旋失败,会降低系统性能
- 如果同步块很短,自旋成功,节省线程挂起切换时间,提升系统性能
- 总结
-
- 偏向锁可用会先尝试偏向锁
- 轻量级锁可用会先尝试轻量级锁
- 以上都失败,尝试自旋锁
- 再失败,尝试普通锁,使用OS互斥量在操作系统层挂起
- ThreadLocal
-
- Thread 中有一个 ThreadLocalMap<ThreadLocal, Object> filed, 这个map实现比较特殊,其实就是一个数组,发生hash冲突的时候,一直向下找;数据的元素是Entry,这个Entry继承WeakReference<ThreadLocal>,Entry的key是ThreadLocal,每次进行读写操作的时候,会进行log2(size)扫描,
JDK8 对并发的新支持
- LongAdder
-
- atomincLong使用方式类似
- 在AtomicLong的基础上实现了热点分离(类似HashMap,减小了同步数据的粒度)
- 动态的实现热点分离(有冲突的时候在新加类似HashMap的Segment)
- CompletableFuture
-
- 实现CompletionStage接口(40余方法)
- Java8中对Future的增强版
- 支持流式调用(stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println()))
- 完成后得到通知(get方法在complete方法调用之前一直阻塞)
-
public static class AskThread implements Runnable {CompletableFuture<Integer> re = null;
public AskThread(CompletableFuture<Integer> re) {this.re = re;}
@Overridepublic void run() {int myRe = 0;try {myRe = re.get() * re.get();} catch (Exception e) {System.out.println(myRe);}}}
public static void main(String[] args) throws InterruptedException {final CompletableFuture<Integer> future = new CompletableFuture<>();new Thread(new AskThread(future)).start();// 模拟长时间的计算过程Thread.sleep(1000);// 告知完成结果future.complete(60);} -
public static Integer calc(Integer para) { //后台起一个线程去执行try {// 模拟一个长时间的执行Thread.sleep(1000);} catch (InterruptedException e) {}return para * para;}
public static void main(String[] args) throws InterruptedException, ExecutionException {final CompletableFuture<Integer> future =CompletableFuture.supplyAsync(() -> calc(50));System.out.println(future.get());}
- StampedLock
-
- 在读写锁基础上进行改进,读操作不阻塞写操作(需配置),仿真写线程饥饿的情况
-
public class Point {private double x, y;private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) {// an exclusively locked methodlong stamp = sl.writeLock();try {x += deltaX;y += deltaY;} finally {sl.unlockWrite(stamp);}}
double distanceFromOrigin() {// A read-only methodlong stamp = sl.tryOptimisticRead();double currentX = x, currentY = y;if (!sl.validate(stamp)) {stamp = sl.readLock();try {currentX = x;currentY = y;} finally {sl.unlockRead(stamp);}}return Math.sqrt(currentX * currentX + currentY * currentY);}} - 实现思想
-
- CLH自旋锁
-
- 锁维护一个等待线程队列,所有申请锁,但是没有成功的线程都记录在这个队列中。每一个节点(一个节点代表一个线程),保存一个标记位(locked),用于判断当前线程是否已经释放锁。
- 当一个线程试图获得锁时,取得当前等待队列的尾部节点作为其前序节点。并使用类似如下代码判断前序节点是否已经成功释放锁:
-
while (pred.locked) {}
- 不会进行无休止的自旋,会在在若干次自旋后挂起线程