其他分享
首页 > 其他分享> > 多线程笔记(二)

多线程笔记(二)

作者:互联网

多线程笔记(二)

1. Synchronized 和 Lock 的区别

2. LockSupport

LockSupport是一个编程工具类,主要是为了阻塞线程(park)和唤醒线程(unpark)时使用

设计原理的核心:许可

​ park:挂起当前线程,等待一个许可

​ unpark:为某个线程提供一个许可,唤醒某个指定的线程

park/unpark和wait/notify很类似,但其具有以下的优点

park/unpark和wait/notify的区别

wait/notify和synchronized联系在一起的,wait过后,线程是进入Blocked状态

park方法使当前线程挂起,进入到waiting状态

3. CAS

CAS(Compare And Swap, 比较并替换)中有3个基本的操作数:V:内存地址的值; A:旧的预期的值;B:要修改的新的值

基本实现方式:

使用CAS去更新一个变量的时候,只有变量的旧的预期的值A 和内存地址的值V 相同的时候,才会将V 修改为新的值B。如果修改失败,会自旋等待,直到修改成功。

CAS实现的基石:Unsafe类

CAS想要保证操作时线程安全的,一个实现的关键在于如何保证 比较并替换 是一个原子操作

在Java中,用Unsafe类来实现CAS的原子操作,Unsafe类 ==> JNI(Java本地接口) ==>本地实现的C++库 ==>操作内存空间

CAS在Java中的应用和缺点

应用:

缺点

​ 自己举个栗子:由于网络延时,线程一线程二都想对内存值A操作,目的就是将内存值A改成B(只修改一次),按理说一个线程操作成功,那另一个线程就要操作失败。线程一和线程二取到的旧的值都是A,假定线程一操作成功,将A改成了B。按理说接下来线程二拿到的内存的值是B,和取到的旧的值A比较,B不等于A,就会提交失败,但是捏,好巧不巧,在线程二修改之前,线程三过来执行它自己的任务,将B改成了A。这个时候线程二拿到的内存值是A,之前取到的旧的值也是A,A等于A,线程二就会对A进行修改。这个时候就对内存值修改了两次,而我们只想让它修改一次,就出错了。可以把内存值想成自己的工资,谁都不想自己的工资被莫名其妙的多改几次把,改多了当我没说,哈哈哈。

ABA的解决方案:给数据加上版本号,每次不仅要比较内存的值,还要比较版本号

4. AQS

AQS是什么?

AQS(AbstractQueuedSynchronizer,抽象队列同步器)是构建锁和其他同步组件的基础框架

AQS能干什么?

基本设计思路

AQS如何把基础功能提供出去?

AQS使用模板方法模式,大概的意思是规定了整体的流程,自己可以具体实现子流程,整体的流程是不能变的。后续把设计模式学了再做补充

非阻塞的获取独占锁的流程

自己画的简化版流程,没有涉及到里面的中断

AQS中获取和释放独占锁和共享锁区别

独占锁:正常情况下,只有持有锁的线程运行结束了,释放锁了,该节点才会出队。

共享锁:当前节点唤醒了下一个节点并且将下一个节点设置尾Head之后,该节点出队。

独占锁:只有在释放锁的时候,才会去看看要不要唤醒下一个节点。

共享锁:在获取锁的过程中会在两个地方看看要不要去唤醒下一个节点。一个是在获取锁的流程中调用setHeadAndPropagate()方法的时候,一个是在释放锁的时候。

5. ReentrantLock

ReentrantLock是Lock接口的实现,主要实现了可重入的独占锁的功能,与synchronized关键字功能类型

ReentrantLock与synchronized对比

ReentrantLock功能更加强大和灵活

公平锁和非公平锁的却别主要体现在获取锁的方式上

公平锁:多个线程按照申请获取锁的先后顺序来获取锁

非公平锁:多个线程按照不是按照申请获取锁的先后顺序来获取锁。比如抢占式获取锁。高并发的情况可能会造成饥饿现象

在ReentrantLock的源码中,公平锁主要是通过判断当前的AQS队列是否有节点来控制当前节点是否获得锁。队列中如果有节点那么tryAcquire()方法直接返回false表示获取锁失败,再将节点其排到队列末尾。

ReentrantReadWriteLock

在实际的业务中,往往读数据比写数据更加频繁,如果我们对读数据使用共享锁,对写数据使用独占锁,那么整个读写的性能就会提高。

读锁:用在读取临界资源的地方

写锁:用在更新临界资源的地方

读锁和写锁的互斥规则:

ReadWriteLock是一个接口,该接口中只有两个方法,分别为Lock readLock();Lock writeLock();

ReentrantReadWriteLock:可重入式读写锁,是读写锁(ReadWriteLock)的实现类。

读写锁的状态存储机制

AQS里的state是一个int值。在读写锁中,需要同时保存两种锁的状态。其同样使用int类型的变量表示state,总共32位,前16位表示读锁的同步状态,后面16位表示写锁的同步状态。获取读锁状态就将state无符号右移16位。获取写锁状态就将state与掩码相与,保留后16位。

6. StampedLock类

ReentrantReadWriteLock中存在着一些问题,写线程可能会出现“饥饿”问题;如果有线程在读,那么写线程是无法获取写锁的。

优点:

在Java8中引入了StampedLock,其对ReentrantReadWriteLock进行了增强,优化了读锁和写锁的访问,使读写锁之间可以相互转换,因此可以更细粒度地控制并发。

缺点:

其设计初衷使作为一个内部工具类来使用,用于辅助开发其他的线程安全组件。用不好的花会产生死锁,产生莫名其妙的问题。不支持可重入也是一个问题。

特点

7. Condition

该接口对原生的wait, notify/notifyAll这些方法进行增强,从Java语言层面,实现类似的功能。

AQS是使用同步队列来控制节点获取锁,在Condition中使用条件队列来控制节点什么时候await(),什么时候signal()。与多个节点共用一个同步队列不同的是,一个Conditon对象就对应一个条件队列。

总体流程为,调用await()时,将节点加入等待队列,然后将线程挂起,等待其他线程对其调用signal()方法。其他线程对其调用signal()方法后,将该节点从条件队列中出队,将其添加到同步队列的末尾,然后将其唤醒。然后就走同步队列的那一套流程。

8. ThreadLocal

ThreadLocal是用来存放线程自身相关数据的一个容器。提供线程本地变量,访问这个变量的每个线程都会有这个变量的一个副本。线程操作数据的时候就会操作线程本地的数据,从而避免了线程安全性问题。

threadLocals其实是一个ThreadLocalMap类型的,在Thread类中的一个属性,伴随的线程的存在而存在。当我们设置ThreadLocal变量的时候,ThreadLocalMap中的key就是ThreadLocal,value就是ThreadLocal变量的值。

标签:AQS,队列,笔记,获取,线程,一个,多线程,节点
来源: https://www.cnblogs.com/xuzhuo123/p/16253912.html