《实战Java高并发程序设计》JDK并发包JUC--第三章

《实战Java高并发程序设计》JDK并发包/JUC–第三章

synchronized的扩展
重入锁:java.util.concurrent.locks.ReentrantLock
public static ReentrantLock lock=new ReentrantLock();

@Overriede

public void run(){

​	lock.lock();try{....}finally{

​	lock.unlock();

}

}

由于这种锁一个线程可以反复的进入,所以叫重入锁

/**
一个线程可以连续两次获得同一把锁
*/
lock.lock();
lock.lock();
try{
    i++;
}finally{
    lock.unlock();
    lock.unlock();
}
重入锁和synchronize的区别

①重入锁对逻辑控制的灵活性要比synchronize好

②重入锁可以提供中断处理能力

中断响应

如果一个线程正在等待锁,那么它依然可以获得一个通知,被告知无需再等待,可以停止工作,这种情况对死锁有帮助。

	例如你和朋友约好一起去打球,如果你等了半个小时朋友还没到,突然一个电话过来说有突发情况不能去了,那么等待这个事就被中断了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XEFEgEOo-1615003700976)(C:\Users\风淡了伤\AppData\Roaming\Typora\typora-user-images\image-20210303214439340.png)]

在这里插入图片描述

锁申请等待限时

通常我们无法判断一个线程为什么迟迟拿不到锁,但如果给定一个等待时间,让线程自动放弃,那么对系统来说是有意义的。我们可以使用tryLock()方法进行一次限时等待。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d6eNU2MK-1615003700987)(C:\Users\风淡了伤\AppData\Roaming\Typora\typora-user-images\image-20210303215451996.png)]

公平锁

公平锁会按照时间的先后顺序,保证先到者先得原则,这样做不会产生饥饿现象。

public static ReentrantLock fairlock=new ReentrantLock(true);

CAS(compare and swap)原则(比较交换原则)

CAS原理就是函数中三个参数,一个是当前内存值A,一个是期望值B,一个是即将更新的值C,当且仅当预期值B和内存值A相等时,则将A的值修改为C的值并返回true,否则什么都不做,并返回false.

ABA问题

如果一个变量V初次读取的时候是A,并在准备赋值的时候检查到它仍然是A,并不是说A就一定没有被改变过,因为有可能在这一段时间内这个值曾经被修改为B,然后又修改回了A,那么CAS操作就会被误认为没有改变过,这个问题就是ABA问题。解决这个问题的核心思想就是加上时间戳来标识不同阶段的数值。比如JUC里面提供的一个原子引用类”AtomicStamedReference",还可以使用传统的互斥同步也就是利用锁的原理。

ReentranLock的几个重要方法整理
Lock()获得锁,如果锁已经被占用,则等待
lockInterruptibly()获得锁,但优先响应中断
tryLock(long time,TimeUnit unit)在给定时间内尝试获得锁,如果成功则返回true,如果失败,则返回
tryLock()尝试获得锁,如果成功,返回true,失败返回false,该方法不等待,立即返回
unlock()释放资源
重入锁的三个要素

①原子状态。原子装填使用CAS操作来存储当前锁的状态,判断锁是否已经被别的线程持有.

②等待队列。所有没有请求到锁的线程会进入等待队列(BlockingQueue)进行等待,待有线程释放锁后,系统就能从等待队列中唤醒一个线程,继续工作。

③阻塞原语park()和unpark()。用来挂起和恢复线程。没有得到锁的线程将会被挂起。

配合重入锁的方法:Condition条件

在synchronized同步中配合使用的是wait和notify(),在重入锁Lock中和这两个方法有相同作用的是await()和signal和signalAll()

await()将当前线程进入等待状态,同时释放当前锁,当其他线程调用signal()时线程重新获得锁继续执行
awaitUninterrupibly()该方法和await()方法基本相同,但是它不会在等待过程中响应中断
signal()用来唤醒一个等待中的线程
public static ReentrantLock lock=new ReentrantLock();
public static Condition condition=new lock.newCondition();
信号量

信号量可以指定多个线程,同时访问某一资源,信号量主要提供以下构造函数

public Semaphore(int permits,boolean fair);
ReadWriteLock读写锁

线程A1,A2进行读操作,线程B1,B2进行写操作,如果重入锁和内部锁理论上说读读、读写、写写之间都是串行的,由于读操作并不对数据的完整性进行破坏,所以我们可以在读读之间利用读写锁,则可以进行并行操作。

读写锁的访问约束

读-读不互斥,可以并行。

读-写,写-写之间都互斥,只能串行。

倒计时器:CountDownLatch

用来控制线程等待,它可以让某一个线程等待直到倒计时结束再开始执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DFeddILV-1615003700993)(C:\Users\风淡了伤\AppData\Roaming\Typora\typora-user-images\image-20210303223445740.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6jAALTPA-1615003700998)(C:\Users\风淡了伤\AppData\Roaming\Typora\typora-user-images\image-20210303223902309.png)]

线程池

为什么使用线程池

①降低资源消耗。通过重复利用已经创建的线程降低线程创建和销毁造成的消耗

②提高响应速度。当任务到达时,任务可以不需要等待线程创建就可以直接执行

③提高线程的可管理性。线程是稀缺资源。如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优以及监控。·

JUC线程池体系

ThreadPoolExecutor类实现了Executor和ExecutorService这两个接口,并有Executors类扮演线程池工厂的角色

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iNRPRwyN-1615003701002)(C:\Users\风淡了伤\AppData\Roaming\Typora\typora-user-images\image-20210303225509717.png)]

ThreadPoolExecutor(int corePoolSize,int  maximumPoolsize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler)
corePoolSize线程池维护线程的最少数量,即常用线程数量
maximumPoolsize:线程池维护线程池的最大线程数量
keepAliveTime当线程池线程数量超过corePoolSize时,多余的空闲线程的存活时间
unitkeepAliveTime的单位
BlockingQueue任务队列,任务被提交但尚未被执行,即等待队列
handler线程池对拒绝任务所使用的策略

线程池的调度策略(增长策略)

当一个任务需要调用线程池中线程进行处理时:

​ 1)如果此时线程池中的数量小于等于corePoolSize,则线程池会创建新的线程来处理被添加的任务

​ 2)如果此时线程池中的线程数量大于corePoolSize,但是等待队列BlockingQueue未满,那么这个任务就会被添加到等待队列当中

​ 3)如果此时线程池中的数量大于corePoolSize,但是等待队列BlockingQueue已满,但是线程池中的线程数量小于maximumPoolSize,则建立新的线程来处理被 添加的任务

​ 4)如果此时的线程数量大于corePoolSize,等待队列已满,并且大于maximumPoolSize,那么就会通过handler来调用拒绝策略来处理

等待队列BlockingQueue 4种

​ **1)**直接提交队列:SynchronousQueue队列,它比价特殊,因为它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一 个删除操作也都要等待对应的插入操作

​ **2)**有界任务队列:ArrayBockingQueue.若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,那么会将新的任务加入 到等待队列当中,如果等待对列已经满了,线程数量没有达到线程池最大线程容量的情况下,则继续创建新的线程,若达到了最大线程数量,则执行拒绝策略

​ **3)**无界任务队列:LinkedBlockingQueue.该队列可以无限制的添加新的任务,线程池创建的常驻线程数量就是corePoolSize,而等待队列可以无限制的增加,也 就是当线程数量达到常驻线程数量之后,就会添加到无界任务队列当中。使用这种队列,一定要注意任务提交和处理之间的协调和控制,不然会出现资源耗 尽

​ 4)优先任务队列:PriorityBlockingQueue.它其实是一个特殊的无界任务队列,只是执行的时候采用的是优先级的顺序执行的。

拒绝策略4种

​ 1)abortPolicy:直接抛异常

​ 2)callerRunsPolicy策略:直接执行当前任务

​ 3)DiscardOledestPolicy策略:丢弃最老的任务(即即将执行的任务),提交当前的任务到队列当中

​ 4)DiscardPolicy策略:直接丢弃当前任务。这种策略最合理

Executors里面主要有以下几种线程池方法

(1)newFixedThreadPool:固定大小线程池,corePoolSize和maximumPoolSize都为用户指定的线程数量,阻塞队列采用LinkedBlockingQueue

(2)newcachedThreadPool:可以无限扩大的线程池,比较适合处理执行时间比较小的任务,采用synchronousQueue装等待任务

(3)newsingleThreadExecotor:单一线程池,它只会创建一条工作线程处理任务,采用LinkedBlockingQueue

(4)ScheduledThreadPool:可调度的线程池,实现周期性调度,比较常用

JDK的并发容器

(1)ConcurrentHashMap,这是一个高效的并发线程安全的hashMap

(2)CopyOnWriteArrayList:这和ArrayList一样,但这是利用了读写分离锁,在读多写少方面性能非常好

(3)ConcurrentLinkedQueue:线程安全的LinkedList

(4)BlockingQueue,通过链表和数组实现的一个接口,表示阻塞队列,非常适合于作为数据共享的通道

(5)ConcurrentSkipListMap:跳表,使用跳表的数据结构进行快速查找

读写分离锁,在读多写少方面性能非常好

(3)ConcurrentLinkedQueue:线程安全的LinkedList

(4)BlockingQueue,通过链表和数组实现的一个接口,表示阻塞队列,非常适合于作为数据共享的通道

(5)ConcurrentSkipListMap:跳表,使用跳表的数据结构进行快速查找

跳表内的所有数据都是有序的,查找时,可以从顶级链表开始找,一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续查找,这也就是说这种查找方式是跳跃式的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值