文章目录
- 一、有三个线程T1,T2,T3如何保证顺序执行
- 二、AQS
- 三、CountDownLatch
- 四、CyclicBarrier
- 五、Semaphore
- 六、自旋锁
- 七、偏向锁
- 八、轻量级锁
- 九、重量级锁
- 十、Synchronized升级流程
- 十一、可重入锁(ReentrantLock)
- 十二、synchronized和Lock区别
- 十三、乐观锁
- 十四、悲观锁
- 总结
一、有三个线程T1,T2,T3如何保证顺序执行
可以使用线程类中的join方法,在一个线程中启动另一个线程,另外一个线程执行完该线程继续执行。T3的run方法中执行t2.join(),T2的run方法中执行t1.join(),这样就会按照T1,T2,T3的顺序执行了。
二、AQS
AQS是java.util.concurrent包下的工具类,全称是AbstractQueuedSynchronizer抽象队列同步器,AQS是多线程同步器,Lock、CountDownLatch、Semaphore都用到了AQS,从本质上来说AQS提供了两种锁机制,分别是排它锁和共享锁。排它锁:就是存在多线程竞争同一共享资源时,同一时刻只允许一个线程访问该共享资源,也就是多个线程中只能有一个线程获得锁资源,比如Lock中的ReentrantLock重入锁实现就是用到了AQS中的排它锁功能。共享锁:也称为读锁,就是在同一时刻允许多个线程同时获得锁资源,比如CountDownLatch、Semaphore都是用到了AQS中共享锁功能。
三、CountDownLatch
CountDownLatch是基于执行时间的同步类,允许一个或多个线程等待其他线程完成操作,构造方法接收一个int参数作为计数器,如果要等待n个点就传入n,每次调用countDown方法时计数器减1,await方法阻塞当前线程直到计数器变为0,由于countDown方法可用在任何地方,所以n个点既可以是n个线程,也可以是一个线程里的n个执行步骤。
public class CountDownLatchDemo { //主线程等待子线程执行完毕 public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(5); for (int i = 0; i { try { Thread.sleep(5000); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + “执行了此任务”); countDownLatch.countDown(); }).start(); } countDownLatch.await(); System.out.println(“执行主线程代码”); }}public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { //子线程等主线程计数器变0一起执行 CountDownLatch countDownLatch = new CountDownLatch(1); for (int i = 0; i { try { countDownLatch.await(); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + “执行了此任务”); }).start(); } Thread.sleep(5000); System.out.println(“执行主线程代码”); countDownLatch.countDown(); }}
四、CyclicBarrier
循环屏障是基于同步到达某个点的信号量触发机制,作 是让 组线程到达 个屏障时被阻塞,直到最后 个线程到达屏障才会解除。构造 法中的参数表示拦截线程数量,每个线程调 await 法告诉CyclicBarrier 已到达屏障,然后被阻塞。还 持在构造 法中传 个 Runnable 任务,当线程到达屏障时会优先执 该任务。适 于多线程计算数据,最后合并计算结果的应 场景。CountDownLacth 的计数器只能 次, CyclicBarrier 的计数器可使 reset 法重置,所以CyclicBarrier 能处理更为复杂的业务场景,例如计算错误时可 重置计数器重新计算。
五、Semaphore
信号量 来控制同时访问特定资源的线程数量,通过协调各个线程以保证合理使 公共资源。信号量可以 于流量控制,特别是公共资源有限的应 场景, 如数据库连接。Semaphore 的构造 法参数接收 个 int 值,表示可 的许可数量即最 并发数。使 acquire 法获得 个许可证,使 release 法归还许可,还可以 tryAcquire 尝试获得许可。
六、自旋锁
synchronized是重量级锁,拿到锁必须要阻塞,当量大的时候会一直阻塞唤醒,优化一下,不要让其阻塞,只是告诉有这么个标记,在synchronized的边界做循环,这就是自旋,如果做了多次循环发现还没有获得锁,再阻塞。
七、偏向锁
有一个线程来访问代码块,没有锁的竞争,偏向某个线程,把偏向锁的偏向标记存储为线程的线程id(主要是同一个线程反复抢占锁的场景),只有一个线程,如果线程抢占那么就会升级成轻量级锁,偏向锁默认是关闭的。
八、轻量级锁
有线程竞争,自旋去判断这个对象的锁是否释放,自旋次数(默认根据上一次自旋次数和锁的释放时间来决定),会升级重量级锁
九、重量级锁
线程阻塞等待。
十、Synchronized升级流程
首先synchronized会尝试使用偏向锁的方式去竞争锁资源,如果能够竞争到偏向锁,表示加锁成功直接返回。如果竞争锁失败,说明当前锁已经偏向了其他线程,需要将锁升级到轻量级锁,在轻量级锁状态下,竞争锁的线程根据自适应自旋次数去抢占锁资源。如果在轻量级锁状态下还是没有竞争到锁,就只能升级到重量级锁,在重量级锁状态下,没有竞争到锁的线程就会被阻塞,线程的状态就是Blocked。
十一、可重入锁(ReentrantLock)
在运行的某个函数或者代码,因为抢占资源或者中断等原因导致函数或者代码的运行中断,等待中断程序执行结束后,重新进入这个函数或者代码中运行,并且运行结果不会受到影响,那么这个函数或者代码就是可重入的。ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义。线程A在第一次抢占到锁,在还没有释放之前再次得到锁,这个时候就不需要重新抢占锁,而是增加重入次数,然后锁需要被释放两次才能获得真正的释放。ReentrantLock是一种可重入的排它锁,主要用来解决多线程对共享资源竞争的问题特性:1)支持可重入。2)支持公平和非公平。3)提供了阻塞竞争锁和非阻塞竞争锁的两种方法,分别是lock()和tryLock()。几个非常关键的技术:锁的竞争,ReentrantLock通过互斥变量,使用CAS机制来实现的,没有竞争到锁的线程,使用了AbstractQueuedSynchronized这样一个队列同步器来存储,底层是通过双向链表来实现的,当锁被释放之后,会从AQS队列里的头部唤醒下一个等待锁的线程。公平和非公平的特性,主要体现在竞争锁的时候,是否需要判断AQS队列存在等待中的线程。等待队列里的锁则是公平锁,如果都可以竞争锁,则是非公平锁。锁的重入特性,在AQS里面有一个成员变量来保存当前获得锁的线程,当同一个线程下次再来竞争锁的时候,就不会去走锁竞争的逻辑,而是直接增加重入次数。
十二、synchronized和Lock区别
十三、乐观锁
对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
十四、悲观锁
对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,直接上锁操作资源。
总结
择一良人,选一城市,三餐四季,春夏秋冬