JUC
2021-02-28 14:39:15 165 举报
AI智能生成
JUC(Java Concurrency Utilities)是Java并发工具包,提供了一组用于多线程编程的实用类和接口。它包括了各种同步器(如锁、信号量等)、线程池、异步任务执行框架以及一些高级并发数据结构。JUC旨在简化Java多线程编程,提高程序性能和可扩展性。通过使用JUC,开发人员可以更容易地实现线程安全的数据结构和算法,避免死锁和竞争条件等问题。总之,JUC是Java程序员在处理并发问题时必不可少的工具包。
作者其他创作
大纲/内容
synchronized
性质
可重入锁,非公平锁,悲观锁,自旋锁
使用
对象,方法,代码块;
锁现象
锁普通方法时,该对象的普通同步方法共用一把锁
锁静态方法时,锁类信息,同步静态方法共用一把锁,与普通同步方法不共享锁
锁代码块时,锁synchronized括号内的对象
无锁的方法调用时不会阻塞
注意:1.锁对象时,同一个类的不同实例拥有不同的锁,不会相互阻塞
2.锁类信息时,类和对象拥有不同的锁,不会相互阻塞
3.一个对象只有一个监视器锁,假如该对象拥有多个synchronized修饰的普通方法,当一个线程正在访问其中的一个同步方法时,其他线程无法访问该对象的其他同步方法
锁静态方法时,锁类信息,同步静态方法共用一把锁,与普通同步方法不共享锁
锁代码块时,锁synchronized括号内的对象
无锁的方法调用时不会阻塞
注意:1.锁对象时,同一个类的不同实例拥有不同的锁,不会相互阻塞
2.锁类信息时,类和对象拥有不同的锁,不会相互阻塞
3.一个对象只有一个监视器锁,假如该对象拥有多个synchronized修饰的普通方法,当一个线程正在访问其中的一个同步方法时,其他线程无法访问该对象的其他同步方法
原理
jvm基于进入和退出Monitor对象来实现方法同步和代码块同步
方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor
代码块的同步是利用monitorenter和monitorexit这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当jvm执行到monitorenter指令时,当前线程试图获取monitor对象的所有权,如果未加锁或者已经被当前线程所持有,就把锁的计数器+1;当执行monitorexit指令时,锁计数器-1;当锁计数器为0时,该锁就被释放了。如果获取monitor对象失败,该线程则会进入阻塞状态,直到其他线程释放锁
对象
组成
对象头,实例数据,对齐数据 +用户自定义属性
实例数据:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐
填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐
对象头:Mark Word +指向对象类型数据的指针+数组长度(如果是数组对象的话,有这一部分,否则没有)
优化
JDK 1.6进行锁优化
锁可以升级但不能降级
锁可以升级但不能降级
无锁状态
偏向锁状态
偏向锁的获取:
判断是否为可偏向状态
如果为可偏向状态,则判断线程ID是否是当前线程,如果是进入同步块;
如果线程ID并未指向当前线程,利用CAS操作竞争锁,如果竞争成功,将Mark Word中线程ID更新为当前线程ID,进入同步块
如果竞争失败,等待全局安全点,准备撤销偏向锁,根据线程是否处于活动状态,决定是转换为无锁状态还是升级为轻量级锁。
判断是否为可偏向状态
如果为可偏向状态,则判断线程ID是否是当前线程,如果是进入同步块;
如果线程ID并未指向当前线程,利用CAS操作竞争锁,如果竞争成功,将Mark Word中线程ID更新为当前线程ID,进入同步块
如果竞争失败,等待全局安全点,准备撤销偏向锁,根据线程是否处于活动状态,决定是转换为无锁状态还是升级为轻量级锁。
偏向锁的释放:
偏向锁使用了遇到竞争才释放锁的机制。偏向锁的撤销需要等待全局安全点,然后它会首先暂停拥有偏向锁的线程,然后判断线程是否还活着,如果线程还活着,则升级为轻量级锁,否则,将锁设置为无锁状态。
轻量级锁
加锁过程:
在代码进入同步块的时候,如果此对象没有被锁定(锁标志位为“01”状态),虚拟机首先在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储对象目前Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word)。然后虚拟机使用CAS操作尝试将对象的Mark Word更新为指向锁记录(Lock Record)的指针。如果更新成功,那么这个线程就拥有了该对象的锁,并且对象的Mark Word标志位转变为“00”,即表示此对象处于轻量级锁定状态;如果更新失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块中执行,否则说明这个锁对象已经被其他线程占有了。如果有两条以上的线程竞争同一个锁,那轻量级锁不再有效,要膨胀为重量级锁,锁标志变为“10”,Mark Word中存储的就是指向重量级锁的指针,而后面等待的线程也要进入阻塞状态
在代码进入同步块的时候,如果此对象没有被锁定(锁标志位为“01”状态),虚拟机首先在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储对象目前Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word)。然后虚拟机使用CAS操作尝试将对象的Mark Word更新为指向锁记录(Lock Record)的指针。如果更新成功,那么这个线程就拥有了该对象的锁,并且对象的Mark Word标志位转变为“00”,即表示此对象处于轻量级锁定状态;如果更新失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块中执行,否则说明这个锁对象已经被其他线程占有了。如果有两条以上的线程竞争同一个锁,那轻量级锁不再有效,要膨胀为重量级锁,锁标志变为“10”,Mark Word中存储的就是指向重量级锁的指针,而后面等待的线程也要进入阻塞状态
解锁过程:
如果对象的Mark Word仍然指向线程的锁记录,那就用CAS操作将对象当前的Mark Word与线程栈帧中的Displaced Mark Word交换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。
如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁比传统重量级锁开销更大。
如果对象的Mark Word仍然指向线程的锁记录,那就用CAS操作将对象当前的Mark Word与线程栈帧中的Displaced Mark Word交换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。
如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁比传统重量级锁开销更大。
重量级锁
Synchronized的重量级锁是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因
线程的创建
继承 Thread类
实现 Runnable接口
无返回值
不能抛出异常
实现run 方法
实现 Callable 接口
有返回值
可以抛出异常
实现 call 方法
线程池创建 Executors【不推荐】
作用:线程复用、可控制最大并发数、管理线程
作用:线程复用、可控制最大并发数、管理线程
四大方法
Executors.newSingleThreadExecutor() 单例线程池
Executors.newFixedThreadPool(5) 固定线程池
newCachedThreadPool() 缓存(收缩)线程池:用则新建,不用则回收
Executors.newScheduledThreadPool(5) 周期线程池:创建一个固定大小线程池,可以定时或周期性的执行任务
七大参数
int corePoolSize
核心线程数
int maximumPoolSize
最大线程数
long keepAliveTime
超时时间
TimeUnit unit
时间单位
BlockingQueue<Runnable> workQueue
缓存队列,存放等待执行的线程
ThreadFactory threadFactory
线程工厂,用于创建线程
RejectedExecutionHandler handler
拒绝策略
四种拒绝策略
AbortPolicy:拒绝任务,并抛出异常,为默认的策略
DiscardPolicy:抛弃当前任务,无异常
DiscardOldestPolicy:抛弃最老的任务,保留刚进来的任务
CallerRunsPolicy:给调用线程池的线程执行任务:A线程调用线程池执行任务,任务满时,溢出的任务交给 A线程去执行,而不是线程池去执行
自定义线程池【推荐】
ThreadPoolExecutor executor = new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
Executors 不推荐使用原因
newSingleThreadExecutor(),newFixedThreadPool()默认了缓存队列的最大长度为 Integer.MAX_VALUE 会造成 OOM
newCachedThreadPool(),Executors.newScheduledThreadPool() 默认了最大线程数为Integer.MAX_VALUE 会造成 OOM
队列 BlockingQueue
ArrayBlockingQueue
数组阻塞队列
DelayQueue
双端队列
LinkedBlockingQueue
链表阻塞队列
SynchronousQueue
没有容量设置,只能存放一个元素
工具类
线程安全容器类
List list= new CopyOnWriteArrayList<>()
Set set = new CopyOnWriteArraySet<>()
Map<String, Object> map = new ConcurrentHashMap<>()
线程同步类
CountDownLatch countDownLatch = new CountDownLatch(3)
countDownLatch.countDown();// 减一
countDownLatch.await();//线程阻塞,当计数器归零候会继续执行
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("龙珠集齐,召唤神龙");
});
System.out.println("龙珠集齐,召唤神龙");
});
cyclicBarrier.await();//等待
cyclicBarrier.reset();//重置
Semaphore semaphore = new Semaphore(3);//信号量
semaphore.acquire();//获取,占用
semaphore.release();//释放
semaphore与线程池最主要的区别:线程池除了控制了并发量,还实现了线程的复用,集中管理线程。semaphore只是控制了并发数
原子类
AtomicInteger: 原子更新整型
int addAndGet(int delta): 以原子的方式将输入的数值与实例中的值相加,并返回结果
boolean compareAndSet(int expect, int update): 如果输入的值等于预期值,则以原子方式将该值设置为输入的值
nt getAndIncrement(): 以原子的方式将当前值加1,注意,这里返回的是自增前的值
void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
int getAndSet(int newValue): 以原子的方式设置为newValue,并返回旧值
AtomicBoolean: 原子更新布尔类型
AtomicLong: 原子更新长整型
AtomicIntegerArray: 原子更新整型数组里的元素
get(int index):获取索引为index的元素值
compareAndSet(int i,E expect,E update): 如果当前值等于预期值,则以原子方式将数组位置i的元素设置为update值
AtomicLongArray: 原子更新长整型数组里的元素
AtomicReferenceArray: 原子更新引用类型数组里的元素
锁
读写锁
ReadWriteLock lock = new ReentrantReadWriteLock()
可重入锁
Lock lock = new ReentrantLock();
锁监视器
Condition condition = lock.newCondition();//这把锁的监视器
四大函数式接口(必须掌握)
- lambda表达式
- 链式编程
- 函数式接口
- Stream流式计算
定义:有且仅有一个抽象方法的接口
注解:@FunctionalInterface
注解:@FunctionalInterface
Function 函数型
输入T , 输出 R
输入T , 输出 R
Predicate 断定型
输入T , 输出Boolean
输入T , 输出Boolean
Comsumer 消费型
输入T ,无输出
输入T ,无输出
Supplier 供给型
无输入,输出 T
无输入,输出 T
Stream 流式编程
案例
流和迭代器类似,只能迭代一次
常见方法
stream() / parallelStream() 将集合转换为流/并行流
filter(T -> boolean) 保留 boolean 为 true 的元素
distinct() 去除重复元素
这个方法是通过类的 equals 方法来判断两个元素是否相等的
如例子中的 Person 类,需要先定义好 equals 方法,不然类似[Person{name='jack', age=20}, Person{name='jack', age=20}] 这样的情况是不会处理的。
如例子中的 Person 类,需要先定义好 equals 方法,不然类似[Person{name='jack', age=20}, Person{name='jack', age=20}] 这样的情况是不会处理的。
sorted() / sorted((T, T) -> int)
如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如 Stream
反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口
反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口
limit(long n) 返回前 n 个元素
skip(long n) 去除前 n 个元素
map(T -> R) 输入元素 T ,返回 R
flatMap(T -> Stream) 将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流
anyMatch(T -> boolean) 流中是否有一个元素匹配给定的 T -> boolean 条件
allMatch(T -> boolean) 流中是否所有元素都匹配给定的 T -> boolean 条件
noneMatch(T -> boolean) 流中是否没有元素匹配给定的 T -> boolean 条件
findAny() 找到其中一个元素
findFirst() 找到第一个元素
reduce((T, T) -> T) 和 reduce(T, (T, T) -> T) 归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出
count() 返回流中元素个数,结果为 long 类型
collect() 返回集合 常用 collect(toList()),collect(toSet())
forEach()
unordered()
ForkJoin
任务拆分思想
ForkJoinPool
ForkJoinTask
如果您觉得此文档对您的学习带来帮助,请点击右上角的👍,谢谢
并发编程三要素
原子性
一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行
可见性
多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果
有序性
程序的执行顺序按照代码的先后顺序来执行
0 条评论
下一页