Java多线程并发
文章平均质量分 86
Java多线程并发
Andy技术支援
技术支援、支持,主要方向:Java、AI、类脑技术、区块链
展开
-
Tomcat的NioEndPoint中ConcurrentLinkedQueue的使用
首先介绍Tomcat的容器结构以及NioEndPoint的作用,以便后面能够更加平滑地切入话题,如图所示是Tomcat的容器结构。Connector是一个桥梁,它把Server和Engine连接了起来,Connector的作用是接受客户端的请求,然后把请求委托给Engine容器处理。在Connector的内部具体使用Endpoint进行处理,根据处理方式的不同Endpoint可分为NioEndpoint、JIoEndpoint、AprEndpoint。原创 2024-01-12 15:28:55 · 487 阅读 · 0 评论 -
ArrayBlockingQueue的使用
在高并发、高流量并且响应时间要求比较小的系统中同步打印日志已经满足不了需求了,这是因为打印日志本身是需要写磁盘的,写磁盘的操作会暂时阻塞调用打印日志的业务线程,这会造成调用线程的rt增加。如图所示为同步日志打印模型。同步日志打印模型的缺点是将日志写入磁盘的操作是业务线程同步调用完成的,那么是否可以让业务线程把要打印的日志任务放入一个队列后直接返回,然后使用一个线程专门负责从队列中获取日志任务并将其写入磁盘呢?原创 2024-01-12 15:15:46 · 786 阅读 · 0 评论 -
信号量Semaphore原理探究
同样下面的例子也是在主线程中开启两个子线程让它们执行,等所有子线程执行完毕后主线程再继续向下运行。输出结果如下。如上代码首先创建了一个信号量实例,构造函数的入参为0,说明当前信号量计数器的值为0。然后main函数向线程池添加两个线程任务,在每个线程内部调用信号量的release方法,这相当于让计数器值递增1。最后在main线程里面调用信号量的acquire方法,传参为2说明调用acquire方法的线程会一直阻塞,直到信号量的计数变为2才会返回。原创 2024-01-12 12:57:21 · 949 阅读 · 0 评论 -
回环屏障CyclicBarrier原理探究
在介绍原理前先介绍几个实例以便加深理解。在下面的例子中,我们要实现的是,使用两个线程去执行一个被分解的任务A,当两个线程把自己的任务都执行完毕后再对它们的结果进行汇总处理。输出结果如下如上代码创建了一个CyclicBarrier对象,其第一个参数为计数器初始值,第二个参数Runable是当计数器值为0时需要执行的任务。在main函数里面首先创建了一个大小为2的线程池,然后添加两个子任务到线程池,每个子任务在执行完自己的逻辑后会调用await方法。原创 2024-01-11 19:24:39 · 786 阅读 · 0 评论 -
CountDownLatch原理剖析
在日常开发中经常会遇到需要在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。在CountDownLatch出现之前一般都使用线程的join()方法来实现这一点,但是join方法不够灵活,不能够满足不同场景的需要,所以JDK开发组提供了CountDownLatch这个类,我们前面介绍的例子使用CountDownLatch会更优雅。使用CountDownLatch的代码如下:输出结果如下。原创 2024-01-11 13:05:15 · 838 阅读 · 0 评论 -
ScheduledThreadPoolExecutor原理探究
Executors其实是个工具类,它提供了好多静态方法,可根据用户的选择返回不同的线程池实例。ScheduledThreadPoolExecutor继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口。线程池队列是DelayedWorkQueue,其和DelayedQueue类似,是一个延迟队列。ScheduledFutureTask是具有返回值的任务,继承自FutureTask。原创 2024-01-11 11:15:57 · 945 阅读 · 0 评论 -
ThreadPoolExecutor原理探究
Executors其实是个工具类,里面提供了好多静态方法,这些方法根据用户选择返回不同的线程池实例。ThreadPoolExecutor继承了AbstractExecutorService,成员变量ctl是一个Integer的原子变量,用来记录线程池状态和线程池中线程个数,类似于ReentrantReadWriteLock使用一个变量来保存两种信息。这里假设Integer类型是32位二进制表示,则其中高3位用来表示线程池状态,后面29位用来记录线程池线程个数。线程池状态:线程池状态含义如下。原创 2024-01-10 18:51:57 · 807 阅读 · 0 评论 -
DelayQueue原理探究
DelayQueue并发队列是一个无界阻塞延迟队列,队列中的每个元素都有个过期时间,当从队列获取元素时,只有过期元素才会出队列。队列头元素是最快要过期的元素。原创 2024-01-09 19:47:24 · 921 阅读 · 0 评论 -
PriorityBlockingQueue原理探究
offer操作的作用是在队列中插入一个元素,由于是无界队列,所以一直返回true。如下是offer函数的代码。如上代码的主流程比较简单,下面主要看看如何进行扩容和在内部建堆。首先看下面的扩容逻辑。这里为啥在扩容前要先释放锁,然后使用CAS控制只有一个线程可以扩容成功?其实这里不先释放锁,也是可行的,也就是在整个扩容期间一直持有锁,但是扩容是需要花时间的,如果扩容时还占用锁那么其他线程在这个时候是不能进行出队和入队操作的,这大大降低了并发性。原创 2024-01-09 19:12:27 · 916 阅读 · 0 评论 -
ArrayBlockingQueue原理探究
向队列尾部插入一个元素,如果队列有空闲空间则插入成功后返回 true,如果队列已满则丢弃当前元素然后返回false。如果e元素为null则抛出NullPointerException异常。另外,该方法是不阻塞的。原创 2024-01-09 17:18:40 · 375 阅读 · 0 评论 -
LinkedBlockingQueue原理探究
向队列尾部插入一个元素,如果队列中有空闲则插入成功后返回true,如果队列已满则丢弃当前元素然后返回false。如果e元素为null则抛出NullPointerException异常。另外,该方法是非阻塞的。原创 2024-01-09 16:28:14 · 894 阅读 · 0 评论 -
ConcurrentLinkedQueue原理探究
先介绍ConcurrentLinkedQueue的几个主要方法的实现原理。原创 2024-01-09 15:48:14 · 748 阅读 · 0 评论 -
StampedLock锁探究
使用乐观读锁还是很容易犯错误的,必须要小心,且必须要保证如下的使用顺序。原创 2024-01-09 10:56:27 · 788 阅读 · 0 评论 -
读写锁ReentrantReadWriteLock的原理
解决线程安全问题使用ReentrantLock就可以,但是ReentrantLock是独占锁,某时只有一个线程可以获取该锁,而实际中会有写少读多的场景,显然ReentrantLock满足不了这个需求,所以ReentrantReadWriteLock应运而生。ReentrantReadWriteLock采用读写分离的策略,允许多个线程可以同时获取读锁。原创 2024-01-09 10:24:49 · 901 阅读 · 0 评论 -
独占锁ReentrantLock的原理
首先代码(4)会查看当前锁的状态值是否为0,为0则说明当前该锁空闲,那么就尝试CAS获取该锁,将AQS的状态值从0设置为1,并设置当前锁的持有者为当前线程然后返回,true。如果锁当前没有被其他线程占用并且当前线程之前没有获取过该锁,则当前线程会获取到该锁,然后设置当前锁的拥有者为当前线程,并设置AQS的状态值为1,然后直接返回。尝试释放锁,如果当前线程持有该锁,则调用该方法会让该线程对该线程持有的AQS状态值减1,如果减去1后当前状态值为0,则当前线程会释放该锁,否则仅仅减1而已。原创 2024-01-08 18:56:13 · 876 阅读 · 0 评论 -
抽象同步队列AQS
比如独占锁ReentrantLock的实现,当一个线程获取了ReentrantLock的锁后,在AQS内部会首先使用CAS操作把state状态值从0变为1,然后设置当前锁的持有者为当前线程,当该线程再次获取锁时发现它就是锁的持有者,则会把状态值从1变为2,也就是设置可重入次数,而当另外一个线程获取锁时发现自己并不是该锁的持有者就会被放入AQS阻塞队列后挂起。需要注意的是,一个Lock对象可以创建多个条件变量。原创 2024-01-08 17:40:52 · 1251 阅读 · 0 评论 -
锁原理剖析-LockSupport工具类
使用诊断工具可以观察线程被阻塞的原因,诊断工具是通过调用getBlocker(Thread)方法来获取blocker对象的,所以JDK推荐我们使用带有blocker参数的park方法,并且blocker被设置为this,这样当在打印线程堆栈排查问题时就能知道是哪个类被阻塞了。,然后添加thread.interrupt();如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。原创 2024-01-08 15:37:59 · 782 阅读 · 0 评论 -
并发List源码剖析
并发包中的并发List只有CopyOnWriteArrayList。CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)上进行的,也就是使用了写时复制策略。在CopyOnWriteArrayList的类图中,每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素,ReentrantLock独占锁对象用来保证同时只有一个线程对array进行修改。原创 2024-01-08 15:02:19 · 1577 阅读 · 0 评论 -
原子操作类原理剖析
前面讲过,AtomicLong通过CAS提供了非阻塞的原子性操作,相比使用阻塞算法的同步器来说它的性能已经很好了,但是JDK开发组并不满足于此。使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS的操作,而这会白白浪费CPU资源。因此JDK8新增了一个原子性递增或者递减类LongAdder用来克服在高并发下使用AtomicLong的缺点。原创 2024-01-08 14:28:47 · 849 阅读 · 0 评论 -
ThreadLocalRandom类原理剖析
ThreadLocalRandom 类是JDK7在JUC包下新增的随机数生成器,它弥补了Random类在多线程下的缺陷。原创 2024-01-08 11:31:28 · 1106 阅读 · 0 评论 -
各种锁的概述
当一个线程要获取一个被其他线程持有的独占锁时,该线程会被阻塞,那么当一个线程再次获取它自己已经获取的锁时是否会被阻塞呢?如果不被阻塞,那么我们说该锁是可重入的,也就是只要该线程获取了该锁,那么可以无限次数地进入被该锁锁住的代码。调用helloB方法前会先获取内置锁,然后打印输出。之后调用helloA方法,在调用前会先去获取内置锁,如果内置锁不是可重入的,那么调用线程将会一直被阻塞。实际上,synchronized内部锁是可重入锁。原创 2024-01-08 10:11:40 · 847 阅读 · 0 评论 -
Cache伪共享
为了解决计算机系统中主内存与CPU之间运行速度差问题,会在CPU与主内存之间添加一级或者多级高速缓冲存储器(Cache)。这个Cache一般是被集成到CPU内部的,所以也叫CPU Cache。在Cache内部是按行存储的,其中每一行称为一个Cache行。Cache行是Cache与主内存进行数据交换的单位,Cache行的大小一般为2的幂次数字节。原创 2024-01-07 21:32:49 · 997 阅读 · 0 评论 -
Unsafe类
思考一下,这里为何要有这个判断?我们知道Unsafe类是rt.jar包提供的,rt.jar包里面的类是使用Bootstrap类加载器加载的,而我们的启动main函数所在的类是使用AppClassLoader加载的,所以在main函数里面加载Unsafe类时,根据委托机制,会委托给Bootstrap去加载Unsafe类。JDK的rt.jar包中的Unsafe类提供了硬件级别的原子性操作,Unsafe类中的方法都是native方法,它们使用JNI的方式访问本地C++实现库。原创 2024-01-07 19:37:49 · 824 阅读 · 0 评论 -
原子性、CAS操作
关于CAS操作有个经典的ABA问题,具体如下:假如线程I使用CAS修改初始值为A的变量X,那么线程I会首先去获取当前变量X的值(为A),然后使用CAS操作尝试修改X的值为B,如果使用CAS操作成功了,那么程序运行一定是正确的吗?Java提供了非阻塞的volatile关键字来解决共享变量的可见性问题,这在一定程度上弥补了锁带来的开销问题,但是volatile只能保证共享变量的可见性,不能解决读一改一写等的原子性问题。ABA问题的产生是因为变量的状态值产生了环形转换,就是变量的值可以从A到B,然后再从B到A。原创 2024-01-07 19:16:58 · 327 阅读 · 0 评论 -
synchronized、volatile关键字
synchronized块是Java提供的一种原子性内置锁,Java中的每个对象都可以把它当作一个同步锁来使用,这些Java内置的使用者看不到的锁被称为内部锁,也叫作监视器锁。线程的执行代码在进入synchronized代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的wait系列方法时释放该内置锁。内置锁是排它锁,也就是当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁。原创 2024-01-07 19:09:01 · 895 阅读 · 0 评论 -
线程安全、共享变量的可见性
最典型的就是计数器类的实现,计数变量count本身是一个共享变量,多个线程可以对其进行递增操作,如果不使用同步措施,由于递增操作是获取一计算一保存三步操作,因此可能导致计数不准确。Java内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间或者叫作工作内存,线程读写变量时操作的是自己工作内存中的变量。那么如何来解决这个问题呢?当一个线程操作共享变量时,它首先从主内存复制共享变量到自己的工作内存,然后对工作内存里的变量进行处理,处理完后将变量值更新到主内存。原创 2024-01-07 19:00:15 · 835 阅读 · 0 评论 -
并发编程线程基础
在讨论什么是线程前有必要先说下什么是进程,因为线程是进程中的一个实体,线程本身是不会独立存在的。进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。操作系统在分配资源时是把资源分配给进程的,但是CPU资源比较特殊,它是被分配到线程的,因为真正要占用CPU运行的是线程,所以也说线程是CPU分配的基本单位。原创 2024-01-05 22:42:39 · 824 阅读 · 0 评论