对于一个程序员来说,需要解决多线程问题,这就需要好好学习下并发了。
并发编程设计比较广泛,那我们就先从线程、进程开始吧
一、线程、进程
二、并发
1、为什么会出现
2、是什么
并发是针对一个共享变量,多个线程同一时间去编辑该共享变量。
3、会出现什么问题
其中一个线程a获取到共享变量x后进行修改,而这一时刻,线程b拿到了共享变量x,也需要进行修改,这样a修改后的值不能被b里面看到
3.并发跟并列的区别
并发是多个线程抢占同一个资源
并列是多个线程,分别使用不同资源,同时运行
简单点来说,对于操作系统来说,并发是多个线程抢占同一个cpu,并列是多个线程在多核cpu下同时运行,每个线程占用一个cpu
生活中例子,我们去买东西,如果只有一个收银窗口,我们就需要排队了,这就是并发。这时候,超市看人多,又开了几个窗口,然后队伍就分为成几个队,这几个队就是并发了
为了能够保证程序的正常运行,就出现了锁
三、锁
1、锁分类
1、按照java代码来分
分为synchronzied这种java自带的关键字,juc下面的并发包,并发包里面类都是基于AQS来的。
四、AQS
1、是什么
AQS就是AbstractQueuedSynchronizer,抽象队列同步器,是JUC的核心基础组件。
它定义了一些公共方法,这些方法解决了同步时的一些细节,通过模板模式,子类通过实现里面的抽象方法,来管理同步状态
2、核心原理
AQS里面定义了一个state状态跟一个FIFO先进先出的双向队列。state表示同步状态,FIFO队列里面存放着拿到锁的线程跟正在排队等待锁的线程。
里面一个核心的数据结构是CLH(Craig, Landin, and Hagersten locks)的改良。
CLH是自旋锁的一个优化,那我们就先从自旋锁开始讲起
五、自旋锁
1、是什么
它是互斥锁的一种实现。具体思想是:一直在循环,直到当前线程成功拿到锁。
自旋锁是不断的检测锁是否被释放,不是让线程挂起或睡觉。
互斥锁是获取锁失败后,将进入到睡眠或阻塞状态
2、实现方式
public void lock() {
Thread currentThread = Thread.currentThread();
// 如果锁未被占用,则设置当前线程为锁的拥有者
while (!owner.compareAndSet(null, currentThread)) {
}
}
public void unlock() {
Thread currentThread = Thread.currentThread();
// 只有锁的拥有者才能释放锁
owner.compareAndSet(currentThread, null);
}
自旋锁使用compareAndSet方法,compareAndSet方法底层是通过CAS实现的,通过比较替换,直到成功获取到锁。
具体CAS讲述,请看 二、并发编程之----CAS
3、优缺点
优点:实现简单,能保证线程一定能拿到锁,同时避免了操作系统进程调度和线程之间的上下文切换。
缺点:
1)锁饥饿问题:在锁竞争激烈的情况下,可能会导致某个线程一直获取不到锁
2)性能低:在多处理器机器上,每个线程对应的处理器都对同一个变量进行读写,而每次读写操作都将要同步每个处理器缓存,导致系统性能严重下降。
适用场景:锁竞争不激烈、锁持有时间短
六、CLH
1、是什么
CLH是自旋锁的改良,进行了哪些改良呢?
1)它定义了一个队列,将线程放入到队列中,这样保证先请求的线程先获取到锁,避免了饥饿问题。
2)每个线程各自轮询自己的本地变量
2、优缺点
优点:
1)公平锁。先入队的线程会先得到锁。
2)实现简单,易于理解。
缺点:
1)没有拿到锁,还是进行了循环等待
2)锁功能单一
3、实现原理
类似一个链表队列,队尾有一个tail指针,当有新的线程进入时,先通过tail获取到队尾的节点,然后新线程加入队尾,并且tail就指向该线程。入队成功后,判断前一个节点是否已经释放锁,如果已经释放,就拿到了该锁。如果没有释放,就一直循环判断前一个节点是否释放锁
4、AQS的CLH
AQS对CLH锁进行了优化。将自旋改为阻塞线程操作,并对锁进行了改造和扩展。
1)扩展了每个节点的状态
每个节点状态分的更细了,有正常等待、超时或取消等。如果前一个队列状态为超时或取消,这个节点就可以使用前面一个节点的状态了
2)将链表队列变成双向队列
将自旋改为阻塞后,需要释放锁的节点会显示的通知下一个节点解除阻塞。
版权归原作者 tuantuanyuyu 所有, 如有侵权,请联系我们删除。