JUC 知识点 - 全集
juc是什么 ?
java.util.concurrent | |
---|---|
java.util.concurrent.locks | 锁相关API |
java.util.concurrent.atomic | 原子性操作api |
线程安全,灵活 |
JUC 分类总结 ?
锁 | 可重入锁 ReentrantLock,读写锁 ReadWriteLock,邮戳锁 StampedLock,等待通知 Condition,阻塞唤醒 LockSupport |
---|---|
原子类 | 基本类型,数组类型,引用类型,字段类型 【AtomicLong】【AtomicLongArray】【AtomicReference】【AtomicReference】 |
阻塞队列 | 非阻塞队列ConcurrentLinkedDeque,阻塞队列 【BlockingDeque,AbstractQueue】,ArrayBlockingQueue,ConcurrentLinkedQueue,同步队列,延迟队列,优先级PriorityQueue |
线程池 | 【单一,可缓存,固定大小,周期】,自定义线程池,【forkjoin poll】分而治之,工作窃取 , future callable |
并发集合 | CopyOnWriteArrayList,CopyOnWriteArraySet,ConcurrentHashMap |
并发工具类 | 限流作用的 信号量 Semaphore,发令枪:CountDownLatch,循环栅栏 CyclicBarrier ,ThreadLocalRandom |
JUC 三大核心思想
AQS | AbstractQueuedSynchronizer 抽象同步器 juc 锁 核心思想 |
---|---|
CAS | CompareAndSwap 比较与替换 不加锁的情况下,实现原子性更新,不需要cpu切换上下文,cpu性能浪费 |
volatile | 可见性,主内存数据,与线程副本内部数保持一致 |
Lock
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iBWRYdfE-1621751537636)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210411155432950.png)]
悲观锁:取数据时,担心数据被其他线程修改,所以悲观的加上一把锁,悲观锁。一刀切的做法,降低性能。线程安全。ReentrantLock
乐观锁:取数据时,不担心数据被其他线程修改,所以不加锁,等待更新时才去检查数据是否被修改。如果检查数据被修改,就更新失败。不切换上下文,浪费cpu。【CAS】
共享锁:读锁,多条线程可以持有线程锁。可以认为不加锁
独占锁:互斥锁,一条线程持有锁。
公平锁:先到先得获取锁,有序获取。降低了性能。
非公平锁:无须,靠运气,提供了性能,
自旋锁:CAS思想:不选自旋,浪费cpu,不会阻塞。
非自旋锁:造成线程等待。
可中断锁:发送信号,中断获取锁,提高了灵活性。
非可中断锁:不能中断获取锁,会一直等待锁的释放。
可重入锁:能获取锁多次,一定程度避免死锁
非可重入锁:不可以多次获取锁,只能获取一次。
ReentrantLock
特点
- 支持了锁超时
- 支持可中断
- 支持公平锁、非公平锁
- 获取到锁的结果
- condition 等待通知
- 可重入的锁
API
lock | 获取锁 |
---|---|
unlock | 释放锁 |
boolean trylock() | 尝试获取锁 |
boolean trylock(time,unit) | 限定时间内获取锁 |
lockInterruptibly() | 相应中断 |
newCondition() | 等待通知队列 |
可重入锁
不可重入:线程只能获取一把锁。容易造成死锁。sun.lock
可重入:线程可以获取多次同一把锁。避免死锁
package com.company;
import sun.misc.Lock;
/**
* 不可重入锁
*/
public class NoLockDemo {
public static void main(String[] args) {
Lock lock=new Lock();
try {
lock.lock();
System.out.println("首次获取锁");
lock.lock();
System.out.println("再次获取锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.company;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 可重入锁
*/
public class NoLockDemo {
public static void main(String[] args) {
Lock lock=new ReentrantLock();
try {
lock.lock();
System.out.println("首次获取锁");
lock.lock();
System.out.println("再次获取锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}
可重入锁原理
内部维护了一个计数器。获取一次锁就加一次。释放一次锁,就减去一次。
怎么算拿到锁?
就是AQS内部 维护了一个 变量 state 等于0 表示无锁,等于1表示获取到锁,大于1 就是可重入锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pyTraeHg-1621751537638)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210413201706542.png)]
公平&非公平锁
公平锁:关心队列,关心顺序,先到先得。打饭
非公平锁:不关心有序,不关心队列情况。土匪
案例演示
package com.company;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平锁 构造方法 true:
*
* 非公平锁 构造方法 false 默认 是 非公平锁:
*
*/
public class ImpartialLockTest {
//构造方法缺省是非公平锁,false 公平锁,true 就是公平锁
Lock lock =new ReentrantLock(true);
public void getLocks() {
lock.lock();//非公平不排队,公平锁会排队
System.out.println(Thread.currentThread().getName()+" 获取到锁");
lock.unlock();
}
public static void main(String[] args) {
//同步资源
ImpartialLockTest tt=new ImpartialLockTest();
new Thread(()->{
tt.getLocks();
},"线程A").start();
new Thread(()->{
tt.getLocks();
},"线程B").start();
}
}
原理
非公平锁:先获取锁通过CAS,如果失败了就在尝试获取锁,尝试获取锁(无锁,获取锁,检查当前线程否是是自己)如果成功就结束,否则就把自己包装成一个Node节点放到阻塞队列里,并且释放掉阻塞。
公平锁:先判断阻塞队列里是否有等待的节点。如果有就执行就会把自己包装成Node节点去等待(可重入检查),否则就通过CAS更新获取锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zCUSUIeA-1621751537642)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210413203139849.png)]
中断获取锁线程
lockInterruptibly() throws InterruptedException //Thread.interrupt()
尝试获取锁&获取锁超时
boolean tryLock :尝试获取锁 ,获取成功返回true,否则返回false,不等待
boolean tryLock(time,unit),指定时间内获取成功返回true,否则返回false,等待
案例
package com.company;
import java.util.concurrent.locks.ReentrantLock;
public class MyTryLock {
public ReentrantLock lock = new ReentrantLock();
public void serviceMethod(){
try {
if (lock.tryLock()) {
// if (lock.tryLock(3,TimeUnit.SECONDS)) {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获取锁定 ");
Thread.sleep(4000);
} else {
System.out.println(Thread.currentThread().getName() + "未获取锁定");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
public static void main(String[] args) {
//同步资源
MyTryLock myTryLock = new MyTryLock();
Runnable runnable = new Runnable() {
@Override
public void run() {
myTryLock.serviceMethod();
}
};
Thread aa = new Thread(runnable, "aa");
aa.start();
Thread bb = new Thread(runnable, "bb");
bb.start();
}
}
获取等待通知,等待队列实例 Condition
Condition newCondition() 代替 object里面的wait和notify() 在多线程场景下更加灵活使用等待通知功能。
ReentrantReadWriteLock 读写锁
读写锁出现的原因:弥补独占锁的不足,公司的场景:读多写少
特性:
公平与非公平
支持锁降级
支持可重入
支持可中断
支持condition
API方面
readLock() | 获取读锁对象 |
---|---|
writeLock() | 获取写锁对象 |
int getReadLockCount() | 读锁数量 |
getWriteHoldCount() | 写锁数量 |
* 读写互斥
* 写写互斥
* 读读不互斥
读写锁原理
写锁: | 通过对state加锁状态进行 与&运算等到写锁的数量,【存在锁的情况】–>【存在读锁,获取写锁就失败】【写锁被其他线程占用,获取写锁也失败】,获取写锁的数量最大是【MAX_COUNT,65535】,否则就去更新锁,获取成功就结束。【不存在锁的情况】–>如果校验锁未被获取,就查看是否有等待队列或者false。然后去更新锁,更新成功就获取锁成功,否则获锁失败。(公平锁校验队列是否有等待任务,非公平直接返回false) 体现出:【读写互斥,写写互斥】 |
---|---|
读锁: | 计算写锁的数量:其他线程占有了写锁,那么获取读锁就失败(读写互斥,写锁正在写的过程,我们不能读)通过二进制右运算获取读锁的数量。获取锁,(等待队列,最大写锁数量,是否cas更新成功,获取锁成功)返回1表示成功,否则调用fullTryAcquireShared(current) 死循环获取到最终值(要不加锁成功,要不加锁失败的) |
state 表示计算读锁的数量和写锁的数量
读锁: return c >>> SHARED_SHIFT; 右运算
写锁:return c & EXCLUSIVE_MASK;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jv7NH8MW-1621751537645)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210417093604300.png)]
缺点:会造成线程饥饿
邮戳锁StampedLock
线程饥饿? 读锁大量占用,写锁始终获取不到,造成写锁线程无法获取到。一直等待。
戳:通过戳校验;是否写锁别获取过。验戳
读/写+乐观读
public long tryOptimisticRead() | 获取乐观读锁,返回一个 “戳” 版本号 |
---|---|
public boolean validate(long stamp) | 验戳 |
public long tryReadLock() | 获取读锁 |
public long tryUnlockWrite() | 获取写锁 |
注意:不支持可重入
注意:不支持Condition 等待通知
读写锁之间互相转化。
邮戳,提高并发
package com.company;
import java.util.concurrent.locks.StampedLock;
/**
* 常用邮戳锁api
*/
public class StampedLockDemo {
public static void main(String[] args) {
StampedLock lock=new StampedLock();
long stamped = lock.tryOptimisticRead();
System.out.println("乐观邮戳号 "+stamped);
long r = lock.tryReadLock();
System.out.println("读锁版本号 "+r);
lock.unlockRead(r);
long w = lock.tryWriteLock();
System.out.println("写锁版本号 "+w);
lock.unlockWrite(w);
boolean validate = lock.validate(stamped);
if(validate){
System.out.println("没有被写锁修改过"+stamped);
}else{
System.out.println("被写锁获取"+stamped);
}
}
}
乐观读:案例
package com.company;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
/**
* 计算总和 解决线程饥饿问题
*/
public class StampedLockDemo {
StampedLock lock=new StampedLock();
int a=10,b=10;
public void sum(){
//乐观读 获取到版本号
long l = lock.tryOptimisticRead();
int a1=a;
int b1=b;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//验戳
if(!lock.validate(l)){
//悲观读
long l1 = lock.readLock();
System.out.println("获取到悲观读锁-----》版本号"+l1);
a1=a;
b1=b;
System.out.println("释放悲观读锁-----》版本号"+l1);
lock.unlockRead(l1);
}
System.out.println("a1="+a1+"b1="+b1+"计算的总和"+(a1+b1));
}
public void udpateInt(int a,int b){
long w = lock.writeLock();
System.out.println("获取到了写锁"+w);
try {
this.a=a;
this.b=b;
} finally {
System.out.println("释放了写锁"+w);
lock.unlockWrite(w);
}
}
public static void main(String[] args) {
StampedLockDemo demo=new StampedLockDemo();
new Thread(()->demo.sum()).start();
new Thread(()->demo.udpateInt(1,2)).start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Condition 等待通知 线程间写作工具
- JUC包出现新的等待通知对象(条件队列)的原因:提供了丰富的api ,强大的扩展功能。
- 相同点:必须要获取锁对象。
- 不同点:object里面api(wait,notify,notifyAll),condition里面的(await,signal,signalAll)
- 获取一个, 可以获取多个条件队列,
- 也支持了 等待超时。
Lock+Condition 替代:synchronized+Object
void await() | 等待,释放锁资源 | |
---|---|---|
boolean await(long time, TimeUnit unit) | 超时等待 | |
void signal() | 随机唤醒 | |
void signalAll() | 全部唤醒 |
package com.company;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 等待通知 Condition
*/
public class ConditionDemo {
public static void main(String[] args) {
ReentrantLock lock=new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"开始等待");
//等待,释放锁
condition.await();
System.out.println(Thread.currentThread().getName()+"释放等待");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}).start();
new Thread(()->{
//前提必须有锁
lock.lock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
try {
System.out.println(Thread.currentThread().getName()+"开始唤醒");
//随机唤醒 一个
condition.signal();
} catch (IllegalMonitorStateException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}).start();
}
}
源码层面:
await(): LockSupport.park(this); 等待,挂起线程,【加入到条件队列,释放锁资源,校验aqs阻塞队列之后,挂起线程】完成了等待
siognal():LockSupport.unpark(node.thread); 核心 唤醒线程 【加入到阻塞队列,aqs队列,就有机会获取到锁】,unpark 唤醒。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tOsxbDVm-1621751537646)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210418161958019.png)]
LockSupport 阻塞/唤醒工具
1, 不需要加锁,直接阻塞和唤醒。
2,唤醒和阻塞的过程可以互换顺序。避免死锁【许可证 0,1】
静态方法 不需要获取对象,直接使用
public static void park() | 阻塞 |
---|---|
public static void unpark(Thread thread) | 释放,唤醒线程 |
package com.company;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
public class LockSupportTest {
public static void main(String[] args) {
Thread t=new Thread(()->{
System.out.println("无限等待的线程");
//不需要加锁
LockSupport.park();
System.out.println("唤醒线程");
});
t.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(t);
}
}
原子类 atomic
多**线程情况下不加锁,仍然保持线程安全,原子更新同步资源,不同中断。 ** java.util.concurrent.atomic
基于 CAS+unsafe+volatile
CAS: 【比较与交换】【compareAndSwap】 主内存,预期值,待更新值, 10==10?20替代10:不更新。
unsafe:操作c,c++库,jni操作,发送指令通过硬件资源,直接操作内存,原子操作
volatile:可见性。
api分类:四个部分【基本类型】【数组类型】【引用类型】【字段类型】 累加器
基本类型 | AtomicInteger,AtomicBoolean,AtomicLong |
---|---|
数组类型 | AtomicLongArray,AtomicIntegerArray,AtomicReferenceArray, |
引用类型 | AtomicReference,AtomicStampedReference,AtomicMarkableReference |
字段类型 | AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater, |
累加器 | LongAdder,DoubleAdder,LongAccumulator,DoubleAccumulator |
原子整形类-AtomicInteger
出现的原因:多线程情况下,仍然保持原子更新操作。无需加锁。 线程安全,(i++ ,多线程情况下出现并发问题)
/**
* 非原子 操作 (笨重)
*/
public class AtomicIntegerDemo {
int i=0;
public synchronized void sum(){
System.out.println(Thread.currentThread().getName()+" "+(i++));
}
public static void main(String[] args) {
AtomicIntegerDemo demo=new AtomicIntegerDemo();
for(int i=0;i<10;i++){
new Thread(()->demo.sum()).start();
}
}
}
package com.company;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 基于CAS 无锁的操作
*/
public class AtomicIntegerDemo {
AtomicInteger atomicInteger=new AtomicInteger();
public void sum(){
System.out.println(Thread.currentThread().getName()+" "+(atomicInteger.getAndIncrement()));
}
public static void main(String[] args) {
AtomicIntegerDemo demo=new AtomicIntegerDemo();
for(int i=0;i<10;i++){
new Thread(()->demo.sum()).start();
}
}
}
API
public final int get() | 得到最新值 |
---|---|
public final int getAndSet(int newValue) | 得到旧值,然后设置成新值 |
public final boolean compareAndSet(int expect, int update) | expect==当前值?替换成update:什么也不做 |
public final int getAndIncrement() | 得到旧值,自增1 |
public final int getAndDecrement() | 得到旧值,减去1 |
public final int getAndAdd(int delta) | 等到旧址,累加delta |
public final int addAndGet(int delta) | 得到新值,累加delta |
package com.company;
import java.util.concurrent.atomic.AtomicInteger;
/**
* api cas AtomicInteger
*/
public class AtomicIntegerDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger();
int andIncrement = atomicInteger.getAndIncrement();
System.out.println("andIncrement="+andIncrement);
System.out.println("get="+atomicInteger.get());
int andAdd = atomicInteger.getAndAdd(3);
System.out.println("andAdd="+andAdd);
System.out.println("get="+atomicInteger.get());
int andDecrement = atomicInteger.getAndDecrement();
System.out.println("getAndDecrement"+andDecrement);
System.out.println("get="+atomicInteger.get());
boolean b = atomicInteger.compareAndSet(7, 6);
System.out.println("比较替换"+b);
System.out.println("get="+atomicInteger.get());
}
}
原子更新数组-AtomicLongArray
更新时:加一个角标字段
public final long getAndSet(int i, long newValue) | 返回旧值,通过角标更新成新值 |
---|---|
public final boolean compareAndSet(int i, long expect,long update) | 返回布尔值,角标:期望值,待更新值 (期望值==当前值?待更新值:什么也不做) |
public final long getAndIncrement(int i) | 返回旧值,通过角标加一 |
public final long getAndDecrement(int i) | 返回旧值,通过角标减一 |
原子更新引用类型- AtomicReference
public final boolean compareAndSet(V expect, V update) | 返回旧值,比较替换成新值 |
---|---|
public final V getAndSet(V newValue) | 返回旧值,设置成新值 |
package com.company;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference reference=new AtomicReference("周杰伦");
boolean b = reference.compareAndSet("周杰伦", "昆凌");
System.out.println("b= "+b);
System.out.println(reference.get());
Object fws = reference.getAndSet("方文山");
System.out.println(fws);
System.out.println(reference.get());
}
}
原子更新字段类型- AtomicIntegerFieldUpdater
public abstract boolean compareAndSet(T obj, int expect, int update) | |
---|---|
public int getAndAdd(T obj, int delta) |
必须:
必须是volatile修饰,
必须是int
不能 static
不能是private
package com.company;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterDemo {
public static void main(String[] args) {
Persion persion=new Persion();
AtomicIntegerFieldUpdater f = AtomicIntegerFieldUpdater.newUpdater(Persion.class,"i");
boolean b = f.compareAndSet(persion, persion.i, 2);
System.out.println(b);
System.out.println(f.get(persion));
int andAdd = f.getAndAdd(persion, 4);
System.out.println(andAdd);
System.out.println(f.get(persion));
}
}
不停自旋的CAS不安全unSafe类
CAS: 【比较与交换】【compareAndSwap】 主内存值,预期值,待更新值, 10==10?20替代10:不更新。
unSafe:不安全类,危险的类,不建议我们使用,能够发送指令操作硬件资源。c,c++库。操作内存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XcLmKHET-1621751537647)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210422210729275.png)]
问题: 不断的自旋,会浪费cpu性能,建议非大量并发时使用。不必上线文切换
ABA问题 A–B--A
解决ABA问题-AtomicStampedReference
API
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) | 期望引用,新值,戳,新戳 |
---|---|
public V getReference() | 得到当前对象应用 |
AtomicMarkableReference ,AtomicStampedReference 都可以解决 ABA 问题。
与生俱来的问题CAS
package com.company;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceDemo {
public static void main(String[] args) {
AtomicStampedReference atomicStampedReference=new AtomicStampedReference("周杰伦",1);
boolean b = atomicStampedReference.compareAndSet("周杰伦", "周润发", 1, 2);
System.out.println(b);
Object reference = atomicStampedReference.getReference();
System.out.println(reference);
int stamp = atomicStampedReference.getStamp();
System.out.println(stamp);
if(stamp!=2){
return;
}
}
}
LongAdder-高并发原子累加器
AtomicLong 为什么还有 LongAdder ?
AtomicLong Cas操作失败,自旋,浪费想能 private volatile int value; 一把手,
LongAdder cells[value] 数组,分担竞争锁资源的压力,分成若干份。 累加 。【没有多线程竞争 一个 bvaseValue。如果是多线程的情况下使用cells[]数组】
构造方法:默认从0开始累加
API
public void increment() | 加一 |
---|---|
public long sum() | 求和 |
package com.company;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
public class LongAdderDemoo {
public static void main(String[] args) {
LongAdder l=new LongAdder();
for (int i=0;i<10000;i++){
new Thread(()->l.increment()).start();
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("求和"+l.sum());
}
}
并发集合-CopyOnWriteArrayList
ArrayList : elementData[size++] = e; 线程不安全,造成数据丢失。
解决方案:
Vector :所有方法都加上synchronized。性能下降。线程饥饿,一把锁
Collections.synchronizedList(list); / synchronized 把一个不安全的集合变成安全的集合 .。性能下降。线程饥饿,一把锁
CopyOnWriteArrayList :写(增删改)的时候复制一个新数组,来保证集合安全、(数据量不大的时候可以用,内存空间的浪费)。
API
addIfAbsent(E e) | 如果存在元素,就不添加,有去重的功能 |
---|---|
public List subList(int fromIndex, int toIndex) | 截取指定范围内的元素(包含头,不包含尾部) |
package com.company;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListDemo {
public static void main(String[] args) {
ArrayList list=new ArrayList();
list.add("周杰伦");
Vector vector=new Vector();
vector.add("");
// vector.get(9);
//把一个不安全的集合变成安全的集合
Collections.synchronizedList(list);
//安全的集合,添加时,增删改复制集合
CopyOnWriteArrayList clist=new CopyOnWriteArrayList();
clist.add("周杰伦");
clist.addIfAbsent("五月天");
clist.add("蔡依林");
clist.addIfAbsent("周杰伦");
ListIterator listIterator = clist.listIterator();
while (listIterator.hasNext()){
Object next = listIterator.next();
System.out.println(next);
}
//截取部分值 【包含头,不包含尾部】
List list1 = clist.subList(0, 1);
System.out.println(list.get(0));
}
}
原理
加锁,复制数组 ,设置新数组指向。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//复制新元素到新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
//指向新数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
并发集合-CopyOnWriteArraySet
hashSet:线程不安全:底部使用的 HashMap
Collections.synchronizedSet(hashSet); 加锁,性能低下。
CopyOnWriteArraySet :CopyOnWriteArrayList.addIfAbsent()api,不能再就加入数据到容器。
并发集合-ConcurrentHashMap
hashMap不能在多线程情况下使用的原因? :并发修改异常,扩容导致闭环,死锁, put 丢数据 【多线程情况-线程池】
不建议使用的系列
Hashtable hashtable=new Hashtable();
Collections.synchronizedMap(new HashMap<>());
//不安全 并发修改异常
HashMap hashMap=new HashMap();
for (int i=0;i<1000;i++){
int finalI = i;
new Thread(()->{
hashMap.put(Thread.currentThread().getName()+"-->"+ finalI, finalI);
System.out.println(hashMap);
}).start();
}
//线程安全
ConcurrentHashMap hash=new ConcurrentHashMap();
for (int i=0;i<1000;i++){
int finalI = i;
new Thread(()->{
hash.put(Thread.currentThread().getName()+"-->"+ finalI, finalI);
System.out.println(hash);
}).start();
}
避免扩容的问题 ?
扩容的数量: int sc = n - (n >>> 2);
不要作为数组使用
/**
map 最大容量
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
默认容量
*/
private static final int DEFAULT_CAPACITY = 16;
/**
* 默认并发等级 锁的数量
*/
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/**
* 负载因子:计算hash值,减少hash 碰撞数次
*/
private static final float LOAD_FACTOR = 0.75f;
/**
* 大于等于8 链表结构转化成红黑数
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 小于6 红黑数 结构转化成 链表
*/
static final int UNTREEIFY_THRESHOLD = 6;
扩容数量计算
// put 30个元素 不是 数组[30]
ConcurrentHashMap cHashMap=new ConcurrentHashMap(40);
cHashMap.put("zjl",48);
int sc = 16 - (16 >>> 2);
System.out.println(sc);
double i=30/0.75;
System.out.println(i);
map简单的原理 put时 ?
1.7版本:分段锁seGement[]锁+hashEntry 内部有链表 腊肉 (Reentrantlock+实体+链表)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-feaCpAbt-1621751537648)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210424155125162.png)]
1.8 版本 CAS+node节点+红黑树
ConcurrentHashMap 原理:
synchronized:性能目前优化的不是很差了。算很好了
Node 节点:包装数据,链表的组成部分
CAS自旋操作:初始化添加首节点使用
红黑树,链表 :小于8个是链表,大于8个转化成 红黑树。
红黑树:性能好,可以减少io次数,提升遍历效率
put时:计算哈希值,初始化table,等到扩容值,,为空的情况下 Cas操作添加原因到 node数组中。否则加查看是否正在扩容,不是扩容,就添加node节点(链表接口,红黑树),如果数量是大于等于8个就转化成红黑树。添加count值。
聊聊并发队列-Queue
队列分类:阻塞队列+非阻塞队列
队列特点:先进先出,后进后出。【优先级队列】
阻塞队列:查看队列容量是否为【空,满】阻塞当前线程等待。直到队列不为空,或者数据不满,才继续操作容器,线程唤醒。
非阻塞队列:队列的容量是无线大的。Integer.MaxValue(); 基于CAS ,非阻塞算法,并发能力都是比较强,数据不需要等待。
阻塞队列:ReentrantLock【悲观策略】,非阻塞队列【CAS冲突监测的乐观锁机制】
阻塞队列&非阻塞事项场景:【生产者消费者】,【线程池】
非阻塞队列API:
ConcurrentLinkedQueue 非阻塞队列 CAS 无锁,无限大容量
阻塞队列API:
阻塞+非阻塞 api
ArrayBlockingQueue | 基于数组+指定容量,有界队列 |
---|---|
DelayQueue | 延迟的队列,到时间自动执行获取元素 |
LinkedBlockingQueue | 基于链表+指定容量 ,有界队列 |
PriorityBlockingQueue | 优先级队列,取数据时,可以排序 |
SynchronousQueue | 同步的队列 |
非阻塞队列-ConcurrentLinkedQueue
非阻塞队列 基于 CAS 并发能力超过阻塞队列,插入到数据尾部,从头获取,线程安全
boolean add(E e) | 添加元素,不能为null,返回布尔值 |
---|---|
boolean offer(E e) | 添加元素,不能为null,返回布尔值 |
E peek() | 获取到元素:偷窥,看看,不删除元素,切返回首元素 |
E poll() | 获取到元素:光明正大,看,删除元素,切返回首元素 |
package com.company;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ConcurrentLinkedQueueDemo {
public static void main(String[] args) {
ConcurrentLinkedQueue queue=new ConcurrentLinkedQueue();
boolean 周杰伦 = queue.add("周杰伦");
System.out.println(周杰伦);
boolean 王力宏 = queue.offer("王力宏");
System.out.println(王力宏);
Object peek = queue.peek();
System.out.println(peek);
//删除了元素
Object poll = queue.poll();
System.out.println(poll);
//效率低
int size = queue.size();
System.out.println(size);
//效率高
boolean empty = queue.isEmpty();
System.out.println(empty);
}
}
原理:offer 添加元素
node.next--->下一个node节点 链表结构
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val) CAS操作+死循环
数组阻塞队列-ArrayBlockingQueue
提供了阻塞功能的方法:take(),pull(),先流的作用。
Interface BlockingQueue
两类方法:阻塞和非阻塞的方法
构造方法:public ArrayBlockingQueue(int capacity) :公平锁非公平锁+容量+初始化数组 【必须写一个容量】
所有的队列不能添加空元素
boolean add(E e) | 添加元素,返回结果 | true,false |
---|---|---|
boolean offer(E e) | 添加元素,返回结果 | true,false |
void put(E e) | 添加元素,阻塞 | |
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException | 添加元素,超时阻塞 |
E peek() | 获取到元素:偷窥,看看,不删除元素,切返回首元素 | |
---|---|---|
poll() | 删除首元素,不会阻塞 | |
public E poll(long timeout, TimeUnit unit) throws InterruptedException | 删除首元素,不会阻塞(一定时间内超时) | |
public E take() throws InterruptedException | 删除首元素,会阻塞 |
package com.company;
import java.util.concurrent.ArrayBlockingQueue;
public class ArrayBlockingQueueDemo {
public static void main(String[] args) {
ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue(1);
boolean add = arrayBlockingQueue.add(3);
System.out.println(add);
boolean offer = arrayBlockingQueue.offer(4);
System.out.println(offer);
int size = arrayBlockingQueue.size();
System.out.println(size);
try {
arrayBlockingQueue.put("32");
System.out.println("32");
} catch (InterruptedException e) {
e.printStackTrace();
}
Object peek = arrayBlockingQueue.peek();
System.out.println(peek);
try {
Object take = arrayBlockingQueue.take();
System.out.println("能打印的日志"+take);
Object take1 = arrayBlockingQueue.take();
System.out.println("打印不出来的日志");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
数组结构
/* 元素存储 */
final Object[] items;
/** 数据元素的数量 */
int count;
/** 一把可重入锁 */
final ReentrantLock lock;
/** Condition 条件队列 */
private final Condition notEmpty;
/** Condition 条件队列 */
private final Condition notFull;
阻塞原理:
pull();notFull.await(); 阻塞当前线程,等待取数据的take()取数据之后,唤醒await()线程(signal 随机唤醒)
take(); notEmpty.await(); 阻塞当前线程,添加数据之后,然后在把自己唤醒。(signal 随机唤醒)
链表阻塞队列-LinkedBlockingQueue
基于 Node 节点:item,next, 链表结构,MAX_VALUE,内存泄漏,指定容量的大小,元素不能为空否则空指针异常,2把锁,锁分离,吞吐量更高
package com.company;
import java.util.concurrent.LinkedBlockingQueue;
public class LinkedBlockingQueueDemo {
public static void main(String[] args) {
LinkedBlockingQueue queue=new LinkedBlockingQueue(2);
queue.offer("周杰伦");
queue.add("昆凌");
System.out.println(queue);
queue.peek();
queue.remove("周杰伦");
try {
queue.put("方文山");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(queue);
}
}
/**
* 实体对象
*/
static class Node<E> {
E item;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;
Node(E x) { item = x; }
}
/** 容量 */
private final int capacity;
/** 数量 节点 */
private final AtomicInteger count = new AtomicInteger();
/**
* 链表的头部
*/
transient Node<E> head;
/**
* Tail of linked list.
* 链表的尾部
*/
private transient Node<E> last;
/** AQS 阻塞队列 取首元素 */
private final ReentrantLock takeLock = new ReentrantLock();
/** 条件队列( 取首元素 ) */
private final Condition notEmpty = takeLock.newCondition();
/** AQS 阻塞队列 添加队列的锁 */
private final ReentrantLock putLock = new ReentrantLock();
/** 条件队列( 添加元素 ) */
private final Condition notFull = putLock.newCondition();
原理:
put: 队列满时,就会 notFull.await();阻塞线程,必须要等待其它线程唤醒阻塞线程。添加队列-- lastnode指向新的node节点,并且修改count值。
take:队列为空时:就会 notEmpty.await(); 阻塞线程,必须要等待其它线程唤醒阻塞线程。first.item = null; 修改成null。
ArrayBlockingQueue&LinkedBlockingQueue 区别
相同点:
ReentrantLock 锁 AQS阻塞队列。
Condition :条件队列 唤醒等待。
不相同点:
前者是一把锁,后者使用了2把锁。读写分离。
数据结构:一个数组,一个是链表,内存占用比较多。
容量部分:都可以执行大小,后者不指定其实就是 无界队列(MAX_VALUE)
优先级队列-PriorityBlockingQueue
什么叫做优先队列:按照我们自定义比较器,来指定元素的顺序,比如银行的vip办理业务,插队打饭。
存储特点:二叉堆+数组+CAS,无界队列【默认是11个大小容量,可以扩容到无界队列MAX_VALUE】【排序之后可以到队列的头部】
二叉堆:【最大堆,最小堆】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5v4eeIpJ-1621751537649)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210428205408584.png)]
API
put 将指定的元素插入到此优先级队列中。由于队列无限制,此方法将永远不会阻止。 |
---|
package com.company;
import java.util.concurrent.PriorityBlockingQueue;
public class PriorityBlockingQueueDemo {
static class Persion implements Comparable<Persion>{
int i;
String name;
public Persion(int i, String name) {
this.i = i;
this.name = name;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int compareTo(Persion o) {
return this.getI()>o.getI()?-1:1;
}
@Override
public String toString() {
return "Persion{" +
"i=" + i +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
PriorityBlockingQueue queue=new PriorityBlockingQueue();
queue.offer(new Persion(10,"周杰伦"));
queue.offer(new Persion(4,"昆凌"));
queue.offer(new Persion(7,"方文山"));
queue.offer(new Persion(2,"王力宏"));
queue.offer(new Persion(9,"林俊杰"));
queue.offer(new Persion(5,"薛之谦"));
try {
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(queue);
}
}
原理:
offer: 获取锁之后,如果需要扩容就释放锁并且基于CAS操作去扩容,否则另一个没有竞争成功的线程就让出CPU时间片。把新数组赋值并且执行新引用queue对象。添加元素,排序。再随机唤醒线程。条件队列 (每次都会唤醒)
延迟队列-DelayQueue
什么是延迟队列:必须过期之后元素才可以取出来 peek(),poll(),take(), 元素要实现 java.util.concurrent.Delayed ,long getDelay(TimeUnit unit) 返回剩余时间,如果小于等于0 表示过期。有序,PriorityQueue (数组结构)
倒计时,取消订单
案例:
package com.company;
import java.sql.Time;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class DelayQueueDemo {
static class DelayedDemo implements Delayed{
int i;
String name;
//过期时间
long exprie;
public DelayedDemo(int i, String name, long exprie) {
this.i = i;
this.name = name;
this.exprie = exprie+System.currentTimeMillis();
}
/**
* 过期计算 convert<=0 表示过期
* @param unit
* @return
*/
@Override
public long getDelay(TimeUnit unit) {
long convert = unit.convert(exprie - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
System.out.println(convert);
return convert;
}
@Override
public int compareTo(Delayed o) {
return this.getDelay(TimeUnit.MILLISECONDS)-o.getDelay(TimeUnit.MILLISECONDS)>=0?1:-1;
}
@Override
public String toString() {
return "DelayedDemo{" +
"i=" + i +
", name='" + name + '\'' +
", exprie=" + exprie +
'}';
}
}
public static void main(String[] args) {
DelayQueue queue=new DelayQueue();
queue.offer(new DelayedDemo(1,"周杰伦",111));
queue.offer(new DelayedDemo(2,"昆凌",10022));
System.out.println(queue);
while (true){
try {
Delayed take = queue.take();
System.out.println(take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
原理:
offer(); 添加元素之后,再头部,就重置线程null,随机唤醒线程。
take(); 死循环+available.await(); 查看队列是否为空,如果为空,就等待,否则就获取超时时长。如果小于等于0就弹出元素。否则,再去查看是否有人等待获取首元素,如果有人等待就睡眠等待,否则,没有等待,等待固定时长,然后自动唤醒自己。再去for 遍历首元素,获取元素。
同步队列-SynchronousQueue
同步队列:线程要去取数据会阻塞到有一个线程把数据添加到队列里。我们才可以取出来。阻塞
特点:阻塞,空集合,api不能使用的,不能判断是不是空isEmpty() true, 迭代器永远是空,无效。peek();
线程池:ExecutorService executorService = Executors.newCachedThreadPool();
案例:
package com.company;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
/**
* 同步队列 put take 阻塞方法 不能是 offer,peek
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
SynchronousQueue queue=new SynchronousQueue();
new Thread(){
@Override
public void run() {
super.run();
try {
/***
* 阻塞子线程 queue.put("周杰伦");
*/
queue.put("周杰伦");
System.out.println("哎呦不错哦!"+queue);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
try {
Thread.sleep(3000);
System.out.println("我起来了~~~~~");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Object take = queue.take();
System.out.println("哈哈哈="+take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
聊聊线程池-Executor
不建议用: new Thread().start();
1, 浪费服务器的资源,创建和销毁 浪费资源
2,不能立马就销毁对象。大量的对象,GC
3, 线程的什么周期比较长,不好管理线程的状态,中断…
4,不支持周期,调度的功能。线程大容易竞争资源
【资源销毁】【不好管理】
**什么是线程池:**维护一批 new thread().start() 放到一个池子或者队列集合里面。提交任务就取出来一条线程直接使用,速度快,结束任务后,把线程返回线程池。或者空闲的线程销毁掉。
**好处:**不用频繁创建和销毁线程。减少浪费资源。
便于管理线程的状态。
支持周期性,提高相应速度。减少资源竞争,
便于控制线程的数量
简单介绍线程池的分类:
Executor | void execute(Runnable command) 顶级接口 ,提交任务 |
---|---|
ExecutorService | 扩展了Executor接口,提供了submit,api |
ForkJoinPool | 工作窃取,密集型补充之前的线程池 |
ThreadPoolExecutor | 自定义线程池的时候使用 |
Executors | 有静态方法,可以返回线程池对象 |
JDK四大内置线程池
//固定大小线程池
package com.company;
import java.util.concurrent.*;
public class ExecurotrsDemo {
public static void main(String[] args) {
//固定大小线程池 3 submit 提交任务 executor(new Runable) oom内存溢出
ExecutorService es= Executors.newFixedThreadPool(3);
for (int i=0;i<5;i++){
es.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
es.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
Future<Object> submit = es.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName());
return "總經理";
}
});
try {
Object o = submit.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
package com.company;
import java.util.concurrent.*;
public class ExecurotrsDemo {
public static void main(String[] args) {
//单个线程池 只有一个线程 ,异常退出的线程,会自动创建。同步的作用 【测试环境使用】 oom内存溢出的风险
ExecutorService es= Executors.newSingleThreadExecutor();
for (int i=0;i<5;i++){
es.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
package com.company;
import java.util.concurrent.*;
public class ExecurotrsDemo {
public static void main(String[] args) {
//一直创建线程 整形最大值,oom[线程数量无限制] , 未使用六十秒的线程将被终止并从缓存中删除【不断的创建线程】
ExecutorService es= Executors.newCachedThreadPool();
for (int i=0;i<45;i++){
es.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
package com.company;
import java.util.concurrent.*;
public class ExecurotrsDemo {
public static void main(String[] args) {
//int corePoolSize 核心线程 oom 周期性质的线程池
ScheduledExecutorService es= Executors.newScheduledThreadPool(5);
for (int i=0;i<2;i++){
//3秒之后执行
for (int i2=0;i2<45;i2++){
es.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},3, TimeUnit.SECONDS);
//第一次是4秒之后,然后每隔三秒执行一次
es.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},4,3, TimeUnit.SECONDS);
}
}
}
自定义线程池
为什么使用自定义线程池:内置的四个线程池【可固定大小的线程池 newFixedThreadPool(int nThreads) 】【单个线程的线程池 xecutorService newSingleThreadExecutor()】【可缓存的线程池 ExecutorService newCachedThreadPool()】 【周期性 ScheduledExecutorService newScheduledThreadPool(int corePoolSize)】 会造成oom。所需要业务自定义线程池
1,队列的大小
2,比如是否运行丢弃任务。
ThreadPoolExecutor
package com.company;
import java.sql.Time;
import java.util.concurrent.*;
public class ExecurotrsDemo {
static class ThreadFactoryDemo implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
Thread t= new Thread(r,"周杰伦自定义的线程");
return t;
}
}
public static void main(String[] args) {
// int corePoolSize, 核心线程池的数量 银行
// int maximumPoolSize, 最大线程池
// long keepAliveTime, 60 空闲时间 :非核心线程
// TimeUnit unit, 时间单位 秒,毫秒,天
// BlockingQueue<Runnable> workQueue,阻塞队列 【ArrayBlockingQueue】【LinkedBlockingQueue】 指定队列大小
// ThreadFactory threadFactory, 线程工程,执行线程名字
// RejectedExecutionHandler handler 拒绝策略【抛出异常,丢弃任务】【抛出异常,不丢弃任务】【丢弃最旧任务,新任务添加队列】【任务主线程运行】
RejectedExecutionHandler handler=new ThreadPoolExecutor.DiscardOldestPolicy();
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(3, 10, 60, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(20), new ThreadFactoryDemo(), handler);
//创建了一个线程 ,核心线程数是3个,最大线程数是10,且队列可以容纳20个,拒绝策略是 丢弃老任务添加到新队列。
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"===========打印周杰伦是自定义线程");
}
});
//配置线程池 【io密集】【cpu密集型】
}
}
线程池添加的流程
// int corePoolSize, 核心线程池的数量 银行
// int maximumPoolSize, 最大线程池
// long keepAliveTime, 60 空闲时间 :非核心线程
// TimeUnit unit, 时间单位 秒,毫秒,天
// BlockingQueue<Runnable> workQueue,阻塞队列 【ArrayBlockingQueue】【LinkedBlockingQueue】 指定队列大小
// ThreadFactory threadFactory, 线程工厂,自定义线程名字
// RejectedExecutionHandler handler 拒绝策略【抛出异常,丢弃任务】【抛出异常,不丢弃任务】【丢弃最旧任务,新任务添加队列】【任务主线程运行】
添加任务:先判断核心线程数是否被沾满,没有沾满就直接交给核心线程执行任务,否则就会把任务放到队列里面,等待核心线程读取。
如果核心+队列都满了。那么就会创建非核心线程去处理任务。一直到【最大线程池满】,【核心+队列+最大线程数】都满的情况下,就开启拒绝策略。
如果超过空闲时间没有任务,那么keepAliveTime 【空闲时间就会起作用】删除掉非核心线程。
线程池的拒绝4大拒绝策略
拒绝策略
【抛出异常,丢弃任务】 ThreadPoolExecutor.AbortPolicy
【不抛出异常,丢弃任务】ThreadPoolExecutor.DiscardPolicy
【丢弃最旧任务,新任务添加队列】 ThreadPoolExecutor.DiscardOldestPolicy
【任务由(调用者)主线程运行】 ThreadPoolExecutor.CallerRunsPolicy
//【抛出异常,丢弃任务】 ThreadPoolExecutor.AbortPolicy
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@5cad8086 rejected from java.util.concurrent.ThreadPoolExecutor@6e0be858[Running, pool size = 10, active threads = 10, queued tasks = 20, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.company.ExecurotrsDemo.main(ExecurotrsDemo.java:42)
【任务主线程运行】 ThreadPoolExecutor.CallerRunsPolicy
周杰伦自定义的线程===========打印周杰伦是自定义线程
main===========打印周杰伦是自定义线程
FutureTask&Future
提供的功能:取消任务结果 ,阻塞主线程获取到结果。【//特点:取消任务+阻塞获取Callable返回值】
boolean cancel(boolean mayInterruptIfRunning) | 尝试取消执行此任务。 |
---|---|
V get() throws InterruptedException, ExecutionException | 等待计算完成,然后检索其结果。 `【阻塞的功能】 |
boolean isCancelled() | true`如果此任务在完成之前被取消 |
boolean isDone() | true 如果这个任务完成 |
Future
package com.company;
import java.util.concurrent.*;
public class FutureDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future<String> submit = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("哎呦不错哦~~~~~");
return "周杰伦";
}
});
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// submit.cancel(false);
try {
System.out.println("是否被取消"+submit.isCancelled());
System.out.println("是否完成任务"+submit.isDone());
if(!submit.isCancelled()){
String s = submit.get();
System.out.println(s);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
package com.company;
import java.util.concurrent.*;
public class FutureDemo {
public static void main(String[] args) {
//特点:取消任务+阻塞获取Callable返回值
ExecutorService executorService = Executors.newFixedThreadPool(3);
FutureTask task=new FutureTask(new Callable<String>() {
@Override
public String call() throws Exception {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("哎呦不错哦~~~~~");
return "周杰伦";
}
});
executorService.submit(task);
try {
Object o = task.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
ForkJoinPool&Fork/Join框架
1,jdk 1.7 ForkJoinPool 处理CPU密集型任务。代替[补充的] threadpoolexecutor 【Fork/Join+ForkJoinPool】
CPU密集型 : 运算,三元,加减乘除。if switch不是io操作【查数据库io】
ForkJoinPool 简单的了解:
线程池,工作窃取或者 分而治之的思想:充分利息CPU多核心的性能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ze0gq2YE-1621751537651)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210502162839959.png)]
Fork/Join可以使用在哪方面?
ForkJoinTask类:+ForkJoinPool 快速的求和
把一个大任务可以拆分成多个小任务。最后在求和得到总数 【拆分小任务+迭代递归思想】
RecursiveTask 处理任务 有返回值,都实现了ForkjoinTask
RecursiveAction 处理任务没有返回值,都实现了ForkjoinTask
fork() :提交任务到队列.执行。
join() :合并,返回结果集
package com.company;
import com.sun.javafx.image.IntPixelGetter;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* ForkJoinTask+ForkjoinPool 计算一个和 通过拆分的方式 [CPU密集型]
*/
public class ForkJoinPoolTest extends RecursiveTask<Integer> {
//最小拆分数量
int min=3;
int start;
int end;
public ForkJoinPoolTest(int start, int end) {
this.start = start;
this.end = end;
}
public static void main(String[] args) {
//Runtime.getRuntime().availableProcessors())类似核心线程
ForkJoinPool pool=new ForkJoinPool();
ForkJoinTask<Integer> submit = pool.submit(new ForkJoinPoolTest(1,1000000));
Integer integer = null;
try {
integer = submit.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(integer);
}
//进行任务拆分且返回一个 sum
@Override
protected Integer compute() {
//不拆分 [范围太小] 减掉后的值,小于 3 1,3 3-1=2<=3 ,8,10=2
if(end-start<=min){
int sum=0;
for(int i=start;i<=end;i++){
sum+=i;
}
return sum;
//拆分
}else{
int mid=(end+start)/2;
ForkJoinPoolTest test1=new ForkJoinPoolTest(start,mid); // 0,5
ForkJoinPoolTest test2=new ForkJoinPoolTest(mid+1,end);//6,10
//拆分
test1.fork();
test2.fork();
//拿到小任务结果
Integer join1 = test1.join();
Integer join2 = test2.join();
int sum= join1+join2;
System.out.println(Thread.currentThread().getName()+"====="+sum);
//返回总和
return sum;
}
}
}
CountDownLatch-同步计数器
什么是同步计数器:让一个线程等待(阻塞/等待wait.await(),其他是线程各自执行完毕自己的任务之后,在唤醒等待线程。
int i=10; --i 0;
场景:第一步校验,开启多线程,第二部批量入库。
void await() | 阻塞等待的意思 |
---|---|
public void countDown() | 类似 i– |
初始值 (count):countDown 就会减掉1.直到成为 0。唤醒
package com.company;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountdownLatchTest {
public static void main(String[] args) {
//构造方法count 线程的数量
CountDownLatch latch=new CountDownLatch(10);
ExecutorService executorService = Executors.newFixedThreadPool(10);
//第一步 校验
for(int i=0;i<10;i++)
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始任务");
//减掉1
latch.countDown();
}
});
try {
//批量入库 不能用 join 等一步 必须同步
System.out.println("开始 线程等待");
//Condition await(); 等于0了之后, 自动唤醒
latch.await();
System.out.println("线程被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
集成了AQS state=10个数量 +CAS 死循环
Semaphore-信号量
什么是信号量:银行的大厅。停车位数量:【限流的作用】
场景:数据库连接池的数量不被 使用完。
acquire() | 获得许可证,如果有可用并立即返回,则将可用许可证数量减少一个 |
---|---|
void release() | 发放许可证,将可用许可证的数量增加一个。 |
package com.company;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreTest {
public static void main(String[] args) {
//只能有10个线程同时执行
Semaphore semaphore=new Semaphore(10);
ExecutorService executorService = Executors.newFixedThreadPool(21);
//第一步 校验
for(int i=0;i<21;i++)
executorService.submit(new Runnable() {
@Override
public void run() {
try {
//减掉1
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"进银行大厅了");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"出银行大厅了");
//加上1 出去了
semaphore.release();
}
}
});
}
}
LockSupport.park(this);阻塞方法 ,LockSupport.unpark(s.thread);
一个是减去1,一个加1,
CyclicBarrier-循环栅栏
什么是循环栅栏:多条线程有一波操作。然后操作完成之后。可以出发一个事件(线程Runable)返回的操作(监听事件)
public int await() throws InterruptedException 等待,减去1
场景:求和:最后一个线程操作 监听事件
package com.company;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
public static void main(String[] args) {
class CycLicDemo extends Thread{
CyclicBarrier cyclicBarrier;
public CycLicDemo(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier=cyclicBarrier;
}
@Override
public void run() {
super.run();
try {
cyclicBarrier.await();
cyclicBarrier.await();
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName()+"睡醒了");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
CyclicBarrier cyclicBarrier=new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
System.out.println("执行完毕");
}
});
for (int i=0;i<3;i++){
new CycLicDemo(cyclicBarrier).start();
}
}
}
原理:await();ReentrantLock,Condition.唤醒所有线程。减掉,如果等于0 就唤醒所有线程,但是在此这前,就会调用run方法 。trip.await();