![](https://img-blog.csdnimg.cn/20201014180756724.png?x-oss-process=image/resize,m_fixed,h_64,w_64)
多线程与高并发
文章平均质量分 83
纵横千里,捭阖四方
弱小,是最大的罪过;没有行动,梦想终究是想一想
展开
-
终于肝了30篇高并发-学习贵在坚持
到今天为止终于将多线程给过了一遍,在发第一篇之前,已经花了比较多的时间做笔记,最近只是将笔记重新分析了一遍,然后拆分成合适的文章逐步完善和发布。高并发与多线程是个非常广阔的大海,而且很多内容学习难度还挺大。这30篇不过是冰山一角。现在就找到了十几个可以继续研究和发表的主题。不过我能暂且告一段落,先学习其他技术,后面再来梳理。原创 2022-09-26 15:37:04 · 234 阅读 · 0 评论 -
30.盘点各种各样的锁
作为本系列的最后一篇,我们今天来盘点一下前面这些章节中涉及的锁:作为高并发的核心主题之一,各种各样的锁伴随我们整个课程 ,现在我们就来梳理一下到底有多少种锁。需要注意的是,这些锁不是简单的包含与被包含的关系,也不是并列关系,而是从不同的角度看就有不同类型的锁。原创 2022-09-26 15:12:09 · 530 阅读 · 0 评论 -
29.安全集合
}}}那ConcurrentHashMap是如何实现线程安全的呢?其机制在JDK7和8中有所不用。JDK1.7中 将一个大的HashMap分成16个segetment,每个segement其实就是一个Hashtable。如果出现冲突会采用线性方式连接。而JDK1.8中取消了segemnt,加锁粒度更小,如下图所示:如果当单个节点的元素比较多时会该节点对应的链改造成红黑树结构。原创 2022-09-26 11:40:05 · 272 阅读 · 0 评论 -
28.线程池
前面介绍的线程池体系其实都属于Java5之后引入的Executor框架。所谓Executor框架,是并发编程中引入的一些线程启动、调度、管理的API,通过这个框架可以很好地分离线程的工作任务和线程的执行过程 ,以及简化线程的基本操作。从图中可以看到,ThreadPoolExecutor线程池实现了Executor和ExecutorService接口,下面简单说一下各个接口和类的含义。原创 2022-09-26 10:45:44 · 190 阅读 · 0 评论 -
27.阻塞队列
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是 从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。在阻塞队列不可用时,这两个附加操作提供了4种处理方式,如表所示。原创 2022-09-25 16:02:26 · 382 阅读 · 0 评论 -
26.原子类操作类
JDK1.5之前,为了保证Java中对单个变量的多个独立操作的原子性和安全性,通常会使用到synchronized锁,但是synchronized需要底层操作系统mutex资源的支持,这是一种重量级资源,性能比较低!JDK1.5的时候,新增了JUC包,增加了许多和同步有关的特性,大大提高了使用Java进行并发编程的效率,比如并发集合、并发队列、新lock锁等。原创 2022-09-25 15:47:33 · 247 阅读 · 0 评论 -
25.CyclicBarrire的功能和作用
CyclicBarrire的意思是可循环(Cyclic)使用的屏障Barrire,主要作用是让一组线程达到一个屏障(也可以称为同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才能继续往下执行,线性进入屏障通过CyclicBarrire的await()方法实现的。原创 2022-09-19 22:59:00 · 253 阅读 · 0 评论 -
24.Semaphore的作用和原理
Semaphore就是信号灯的意思,主要功能是用来限制对某个资源同时访问的线性数量,它有两个核心方法:acquire()方法,获取一个令牌。release()方法,释放一个令牌。如下图所示,当多个线程访问某个限制访问流量的资源时,需要先调用acquire()方法获得一个访问令牌,如果能正常获得,则表示允许访问,如果令牌不够,则会阻塞当前线程。原创 2022-09-19 22:53:50 · 578 阅读 · 0 评论 -
23.CountDownLatch的应用和原理
我们先看一下如何使用CountDownLatch。上面的代码构建了一个倒计时为2的countDownLatch实例。定义两个线程分别执行RelationService线程,在线程中调用countDownLatch.countDown()方法,表示对倒计时进行递减,其实也可以认为当前线程的某个任务执行完毕。最后在main()方法中调用countDownLatch.await()进行阻塞,当计数器为0时被唤醒。该类的使用类似Thread.join(),但是比其更加灵活。原创 2022-09-19 22:48:13 · 262 阅读 · 0 评论 -
22.Condition的功能和原理
前面我们比较完整的介绍了重入锁的原理和实现过程,本开始,我们梳理几个的比较重要的并发工具:Condition、CountDownLatch、Semaphone、CyclicBarrier和原子类。本章我们先看Condition。Condition在业务代码中使用的并不算多,但是在很多开源框架的源码中有大量应用。它的作用和wait()/notify()方法相同,都是基于某个条件去等待和唤醒,所以可以认为两者作用基本一致。原创 2022-09-19 22:34:57 · 701 阅读 · 0 评论 -
22.读写锁ReetrantReadWriteLock
我们接下来从整体上看一下读写锁的基本过程。假设两个线程ThreadA和ThreadB先去获得读锁。此时使用firstReader和firstReaderHoldCount分别记录第一个获得读锁的线程以及线程重入的次数。ThreadB获得读锁,用HoldCounter记录当前线程的重入次数。接着ThreadC来抢占写锁,由于此时有ThreadA和ThreadB持有读锁,因此ThreadC抢占写锁失败,直接加到同步阻塞队列中。此时假如又来了D和E来抢占写线程。原创 2022-09-18 23:12:13 · 226 阅读 · 0 评论 -
21.Lock锁原理
理解AQS是打开JUC的钥匙,而如果要知道如何开门,那必须搞清楚Lock加锁和释放锁的整个过程到底是怎样的。理解了AQS的功能之后,我们再来看一下Lock锁以及几种典型锁的完整实现过程。其中look就是加锁,而tryLock就是尝试加锁,释放锁就是unlock,condition的问题,我们后面再说。在Lock接口上的注释也注明了,实现该接口主要是单个类:重入锁ReentrantLock、读写锁ReadWriteLock,另外从jdk8开始又增加了优化的读写锁StampedLock。原创 2022-09-18 11:32:36 · 2012 阅读 · 0 评论 -
17.Thread.join的用法和原理
在应用程序中,如果某段程序希望等待前面的线程执行结束后再执行,并发编程里有很多工具可以做,其中Join()就可以。join的作用就是让线程的执行结果对后续线程的访问可见。为什么会这样呢?这就是因为正常情况下,子线程还没执行,main线程已经完了,而加了thread.Join就会让主线程等待,直到子线程的任务都完成之后再继续进行Join()之后的内容。原创 2022-09-04 16:44:42 · 261 阅读 · 0 评论 -
16.线程通信1:生产者/消费者问题
线程之间的通信又称为线程同步,是指当某个线程修改了一个对象的值时,另外一个线程能感知到该值的变化并进行相应的操作。实现线程之间通信的方法有:1.基于volatile修饰的共享变量2.通过wait和notify机制3.Thread.Join方法4.使用synchronized同步关键字5.Condition.await/signal方法Java提供了(wait/notify)等待/通知机制来实现多个线程之间的协同处理,也就是控制线程之间的等待和唤醒。原创 2022-09-04 15:43:03 · 246 阅读 · 0 评论 -
18.透彻理解死锁
synchronized同步锁,虽然能解决线程安全的问题,但是如果使用不当,就可能导致死锁,也即请求被阻塞而一直无法返回。除了死锁,还有个活锁的情况,我们看一下概念的区别:死锁: 一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。活锁: 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。也就是“生不如死”的状态,不过处于活锁的实体是在不断的改变状态,活锁有可能自行解开。这四个条件同时满足,就会产生死锁。原创 2022-09-04 17:15:11 · 191 阅读 · 0 评论 -
19.Lock锁介绍与应用案例
在JUC中,Lock是一个接口,其作用与synchronized类似,都是为了保证线程安全而提前加锁,不同的是Lock只定义了与抢锁和释放锁相关的操作,没有具体实现,而且要用lock()和unlock()将要保护的代码给包起来才可以。其中常用的接口有三个:lock():抢占锁资源方法,如果当前线程没有抢占到锁,则阻塞。trylock():尝试抢占锁资源,如果抢占成功则返回true,否则返回false。unlock():释放锁。原创 2022-09-05 22:39:09 · 966 阅读 · 0 评论 -
20.AQS原理
重入锁的实现一个比较复杂的过程,涉及多个类和方法。而这还只是AQS的一小部分,要真正理解JUC体系 ,我们必须先梳理清楚AQS的问题。AQS, Abstract Queued Synchronizer ,即抽象队列同步器。JUC里提供的线程工具中,很大一部分都基于AQS来实现的,甚至可以说AQS是整个JUC体系的根基。原创 2022-09-07 08:57:09 · 274 阅读 · 0 评论 -
15.ThreadLocal的作用
在多线程环境中,如果多个线程同时访问某个变量,如果希望每个线程对共享变量的相关操作仅对自己可见 ,该如何做呢?对应的现实场景就是各有各家的炉灶,相互之间生火做饭互不影响,这就是ThreadLocal要干的事。ThreadLocal为每个线程提供一个独立的空间,用来存储共享变量的副本,每个副本只会对共享变量的副本进行操作,线程之间互不影响。这段代码虽然比较长,但是功能很简单, 首先定义了一个全局变量string,并初始化为"DEFAULT VALUE"。原创 2022-09-02 08:25:15 · 329 阅读 · 0 评论 -
14. happens-before模型
(1)程序顺序规则1.不能改变程序的执行结果(在单线程环境下,执行的结果不变.) 2.依赖问题, 如果两个指令存在依赖关系,是不允许重排序 例如,这里的c=a*b一定要求前两个赋值完成才可以执行,但是这里并没要求b=1要早与a=1。int a=1;aint b=1;bint c=a*b;c}描述就是“b happens before c”。需要注意的是,这个的含义是c执行的时候b的结果已经是可见的了,而不是规定指令执行的顺序。(2)传递性规则。原创 2022-09-02 00:11:19 · 244 阅读 · 0 评论 -
13.深入浅出高速缓存带来的可见性问题
除了编译器优化等导致重排序,还有一种情况也会导致可见性问题,那就是高速缓存的存在,本节我们就来分析一下。本节涉及的内容,例如缓存一致性协议等,在网上能找到很多介绍,但是其转移过程都非常复杂,我们重点深入浅出解释该问题。原创 2022-09-02 00:03:41 · 557 阅读 · 0 评论 -
12.重排序带来的可见性问题
对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排 序(不是所有的编译器重排序都要禁止)。对于处理器重排序,JMM的处理器重排序规则会要 求Java编译器在生成指令序列时,插入特定类型的内存屏障(Memory Barriers,Intel称之为 Memory Fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序。JMM属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。3)内存系统的重排序。.原创 2022-09-01 08:50:59 · 203 阅读 · 0 评论 -
11.一个诡异的可见性问题
Thread.sleep(0)生效的原因是导致线程切换,线程切换会导致缓存失效从而读取到了新的值。在单线程的环境下,如果向一个变量先写入一个值,然后在没有写干涉的情况下读取这个变量的值,那这个时候读取到的这个变量的值应该是之前写入的那个值。但是在多线程环境下,读和写发生在不同的线程中的时候,可能会出现:读线程不能及时的读取到其他线程写入的最新的值,这就是可见性。这主要是指令执行过程中存在重排序导致的,Server版本的编译器是面向服务器的,会做大量的优化,例如勿用代码消除,循环展开、消除公共子表达式等等。.原创 2022-09-01 08:38:13 · 107 阅读 · 0 评论 -
10.synchronized锁升级的过程
之所以有三种锁,并且能提高性能, 这是因为HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同 一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁,如果出现了少量竞争就用轻量级锁,最后才用重量级锁。那具体在升级的过程是怎么样的呢?我们接下来就详细分析。在理解该问题之前,我们再看一下平时占位子的图:对象就是在堆空间放着的座位,而要占位子的人就是线程。我们怎么知道某个位子有没有人占就是看一下这个位子上面是否有占位标记。...原创 2022-08-31 23:29:47 · 102 阅读 · 0 评论 -
9.synchronized的三把锁
我们知道,同步锁无非是多个线程抢占一个资源,如果抢占成功就获得了锁,失败的线程则阻塞等待,直到获取到锁被释放再重新抢。貌似该过程就只有一种情况 ,但是观察上面的对象头结构,会发现里面标记了偏向锁、轻量级锁和重量级锁三种,又表示什么呢?其实在jdk6之前,synchronized确实只有一种方式——重量级锁,其基本原理是使用底层操作系统的MutexLock来实现,该过程会把当前线程挂起,并从用户态切换到内核态。其问题是开销太大、性能不足,因为很多场景可能不需要这么重的操作。......原创 2022-08-31 23:20:05 · 335 阅读 · 0 评论 -
8.对象与锁标记
synchronize既然能实现互斥,那其内部是如何做的呢?我们逐步分析。问题1:根据前面的说明,要实现一个互斥的锁,存在多个线程同时抢占同一个资源的情况,此时如何允许只有一个线程得到资源,而其他线程只能等待呢?等待的线程应该做什么呢?问题2:等待的线程此时什么都做不了,因此需要让其释放CPU资源,避免浪费,该如何实现呢?假如抢资源的线程很多,我们该如何管理这些等待的线程呢?第三个问题:如果多个线程一次只能有一个获得锁,效率可能会很低。......原创 2022-08-31 09:08:48 · 345 阅读 · 0 评论 -
7.synchronized锁的应用
这个问题还要回到JVM中关于堆内存分配策略中,Class类是JVM在启动过程中加载的,每个.class文件被装载后产生一个唯一的Class对象,通过static修饰的对象和成员方法都属于类级别,它们会随着类的定义被分配、装载和回收,可以理解为Class对象会伴随JVM的整个生命周期,一般不会被回收。而实例对象是伴随实例的创建而开始,随着实例的回收而消失,整个过程是JVM的堆空间管理的。另外一种方式是对象锁,也就是针对堆中创建的具体实例的锁,如果根据同一个类创建了多个实例,则对象锁就可能存在多个。...原创 2022-08-31 08:28:21 · 162 阅读 · 0 评论 -
6.透彻理解UnSafe类
UnSafe可以绕开JVM,直接访问操作系统层,甚至汇编层的功能,从而可以提高程序效率。原创 2022-08-28 09:28:40 · 163 阅读 · 0 评论 -
5.CAS原理
我们深入分析CAS以及相关的延伸问题。CAS是乐观锁的重要原理,在后面很多内容中都会用到。原创 2022-08-25 09:11:09 · 211 阅读 · 0 评论 -
4.如何终止线程
本文介绍了线程终止的问题 ,特别是阻塞线程的终止。主线程想让子线程停止要通过interrupt给子线程发一个信号,告诉要中断了,而不是强制将其中断。触发复位是为了让子线程保持原来的状态,是否中断则有子线程在catch中决定。如果不处理就不中断,如果要中断就是再执行一次Thread.currentThread().interrupt();原创 2022-08-24 08:56:04 · 4748 阅读 · 0 评论 -
3.Java线程的状态
本文介绍了多线程的6种状态 ,特别是Blocked、waiting和time_waiting三种状态的特征和相互之间的关系原创 2022-08-24 08:21:19 · 1842 阅读 · 1 评论 -
2.如何创建线程
创建线程的方式主要有三种:实现Runnable接口、继承Thread类或者实现Callable接口,我们一个个来看一下。当然还能使用线程池来创建,这个后面单独讨论。原创 2022-08-24 08:17:10 · 143 阅读 · 0 评论 -
1.并发编程的本质问题
本章主要介绍了多线程与高并发相关的概念、原理、常见问题和核心设计等方面的问题,理解这些内容有助于我们后面逐步建立起完整的多线程技术体系。本部分将深入分析Java多线程的核心基础问题,本章我们会大量研究JDK的核心设计,并且阅读大量的源代码,逐步梳理出整个并发系统的核心逻辑。原创 2022-08-24 08:13:50 · 288 阅读 · 0 评论