文章目录
AQS是JUC下大量工具的基础类,很多工具都基于AQS实现的,比如lock锁,CountDownLatch,Semaphore,线程池等等都用到了AQS。
AQS中有一个核心属性state,还有一个双向链表以及一个单向链表。其中state是基于volatile修饰,再基于CAS修改,可以保证原子,可见,有序三大特性。单向链表是内部类ConditionObject对标synchronized中的等待池,当lock在线程持有锁时,执行await方法,会将线程封装为Node对象,扔到Condition单向链表中,等待唤醒。如果线程唤醒了,就将Condition中的Node扔到AQS的双向链表等待获取锁。
当持有资源的线程执行完成后,需要在AQS的双向链表拿出来一个,如果head的next节点取消了
如果在唤醒线程时,head节点的next是第一个要被唤醒的,如果head的next节点取消了,会出现节点丢失问题。
如下图,当一个新的Node添加到链表时有3个步骤,当第三个步骤还未完成时,如果从head开始就找不到需要被唤醒的节点了。

因为AQS中,存在取消节点的操作,如果使用双向链表只需要两步
但是如果是单向链表,需要遍历整个单向链表才能完成的上述的操作。比较浪费资源。
每个Node都会有一些状态,这个状态不单单针对自己,还针对后续节点
但是一个节点无法同时保存当前节点状态和后继节点状态,有一个哨兵节点,更方便操作。
ReentrantLock是基于AQS实现的。
公平锁和非公平中的lock方法和tryAcquire方法的实现有点不同,其他都一样
如果都没拿到锁,公平锁和非公平锁的后续逻辑是一样的,加入到AQS双向链表中排队。
如果一个操作写少读多,还用互斥锁的话,性能太低,因为读读不存在并发问题。读写锁可以解决该问题。
ReentrantReadWriteLock也是基于AQS实现的一个读写锁,但是锁资源用state标识。如何基于一个int来标识两个锁信息,有写锁,有读锁,怎么做的?
一个int,占了32个bit位。在写锁获取锁时,基于CAS修改state的低16位的值。在读锁获取锁时,基于CAS修改state的高16位的值。
写锁的重入,基于state低16直接标识,因为写锁是互斥的。读锁的重入,无法基于state的高16位去标识,因为读锁是共享的,可以多个线程同时持有。所以读锁的重入用的是ThreadLocal来表示,同时也会对state的高16为进行追加。
ArrayBlockingQueue:底层基于数组实现,记得new的时候设置好边界。
LinkedBlockingQueue:底层基于链表实现的,可以认为是无界队列,但是可以设置长度。
PriorityBlockingQueue:底层是基于数组实现的二叉堆,可以认为是无界队列,因为数组会扩容。
ArrayBlockingQueue,LinkedBlockingQueue是ThreadPoolExecutor线程池最常用的两个阻塞队列。
虚假唤醒在阻塞队列的源码中就有体现。

比如消费者1在消费数据时,会先判断队列是否有元素,如果元素个数为0,消费者1会await挂起。此处判断元素为0的位置,如果用if循环会导致出现一个问题。
解决方案,将判断元素个数的位置,设置为while判断。
核心线程数,最大线程数,最大空闲时间,时间单位,阻塞队列,线程工厂,拒绝策略
线程池有5个状态:RUNINING、SHUTDOWN、STOP、TIDYING、TERMINATED

线程池的状态是在ctl属性中记录的。本质就是int类型

AbortPolicy:丢弃任务并抛异常(默认) 
CallerRunsPolicy:当前线程执行

DiscardPolicy:丢弃任务直接不要

DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务

一般情况下,线程池自带的无法满足业务时,自定义一个线程池的拒绝策略,实现下面的接口即可。

核心线程不是new完就构建的,是懒加载的机制,添加任务才会构建核心线程


避免线程池出现队列有任务,但是没有工作线程处理。
当核心线程数是0个,任务进来后会到阻塞队列,但是没有工作线程,此时空任务的非核心线程就可以处理该任务。
##3.6 在没任务时,线程池中的工作线程在干嘛?
如果是核心线程,默认情况下,会在阻塞队列的位置执行take方法,直到拿到任务为止。
如果是非核心线程,默认情况下,会在阻塞队列的位置执行poll方法,等待最大空闲时间,如果没任务,删除线程,如果有活,那就正常干。
首先出现异常的工作线程不会影响到其他的工作线程。
工作线程的本质,就是Worker对象。继承AQS跟shutdown和shutdownNow有关系。
如果是shutdown,会中断空闲的工作线程,基于Worker实现的AQS中的state的值来判断能否中断工作线程。如果工作线程的state是0,代表空闲,可以中断,如果是1,代表正在干活。
如果是shutdownNow,直接强制中断所有工作线程
线程池的目的是为了减少线程频繁创建/销毁带来的资源消耗,充分发挥CPU的资源,提升整个系统的性能。不同业务的线程池参考的方式也不一样。
所以线程池的参数设置需要通过压测以及多次调整才能得出具体的。
CountDownLatch本质其实就是一个计数器。在多线程并行处理业务时,需要等待其他线程处理完再做后续的合并等操作时,可以使用CountDownLatch做计数,等到其他线程出现完之后,主线程就会被唤醒。实现过程如下:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch count = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + finalI + "执行中");
count.countDown();
}).start();
}
count.await();
System.out.println("所有线程都执行完成了");
}
}
信号量,就是一个可以用于做限流功能的工具类。比如要求当前服务最多3个线程同时干活,将信号量设置为3。每个任务提交前都需要获取一个信号量,获取到就去干活,干完了,归还信号量。实现过程如下:
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
try {
semaphore.acquire();
System.out.println("线程" + finalI + "执行中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
new Thread(() -> {
try {
long begin = System.currentTimeMillis();
semaphore.acquire();
long end = System.currentTimeMillis();
System.out.println("限流了" + (end - begin) + "ms");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
CopyOnWriteArrayList写数据时,是基于ReentrantLock保证原子性的。写数据时,会复制一个副本写入,写入成功后,才会写入到CopyOnWriteArrayList中的数组,保证读数据时,不要出现数据不一致的问题。
缺点就是:如果数据量比较大时,每次写入数据,都需要复制一个副本,对空间的占用太大了。如果数据量比较大,不推荐使用CopyOnWriteArrayList。
适合写操作要求保证原子性,读操作保证并发,并且数据量不大的场景。
JDK1.7里有环(扩容时)。
并发添加数据时会覆盖,数据可能丢失。
在记录元素个数和HashMap写的次数时,记录不准确。
数据迁移,扩容,也可能会丢失数据。
ConcurrentHashMap是懒加载的机制,而且大多数的框架组件都是懒加载的~
基于CAS来保证初始化线程安全的,这里不但涉及到了CAS去修改sizeCtl的变量去控制线程初始化数据的原子性,同时还使用了DCL,外层判断数组未初始化,中间基于CAS修改sizeCtl,内层再做数组未初始化判断。

负载因子是0.75从两个方面去解释。为啥不是0.5,为啥不是1?
0.5:如果负载因子是0.5,数据添加一半就开始扩容了
1:如果负载因子是1,数据添加到数组长度才开始扩容
0.75就可以说是一个居中的选择,两个方面都兼顾了。
再就是泊松分布,在负载因子是0.75时,根据泊松分布得出,链表长度达到8的概率是非常低的,源码中的标识是0.00000006,生成红黑树的概率特别低。虽然ConcurrentHashMap引入了红黑树,但是红黑树对于写入的维护成本更高,能不用就不用,HashMap源码的注释也描述了,要尽可能规避红黑树。
一般情况下不会造成阻塞。
如果在put操作时,发现当前索引位置并没有数据时,正常把数据落到老数组上。
如果put操作时,发现当前位置数据已经被迁移到了新数组,这时无法正常插入,去帮助扩容,快速结束扩容操作,并且重新选择索引位置查询

这里是基于LongAdder的机制实现,但是并没有直接用LongAdder的引用,而是根据LongAdder的原理写了一个相似度在80%以上的代码,直接使用。
LongAdder使用CAS添加,保证原子性,其次基于分段锁,保证并发性。
无论查哪,都不阻塞。
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类