JUC学习
什么是JUC
java.util.concurrent
线程的状态
new:新建
runnable:准备就绪
blocked:阻塞
waiting:等待(不见不散)
timed_waiting:指定时间等待,超过时间不等(过时不候)
terminated:终结
wait和sleep的区别
- sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用
- sleep不会释放锁,他也不需要占用锁,wait会释放锁,但调用它的前提是当前线程占有锁(即代码在synchronized中)
- 他们都可以被interrupted中断
串行/并行/并发
串行模式
串行表示所有任务都一 一按先后顺序执行,串行意味着必须先装完一车柴才能运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了以上步骤才能进行下一个步骤,简单来说就是串行是一次只能取得一个任务,并执行这个任务。
并行模式
并行意味着同时取得多个任务,并同时去执行所取得的这些任务,并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度,并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度则依赖于多核CPU。
并发模式
并发(concurrent)指的是多个程序可以同时运行的现象,更细化的是多进程可以同时运行或多指令可以同时运行。以下举例说明:
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点。例如:春运抢票,电商秒杀等
并行:多项工作一起执行,之后再汇总。例如:泡方便面、电水壶烧水、一边撕调料导入桶中。
管程
Monitor:监视器,是一种同步机制,保证在同一个时间,只有一个线程访问被保护数据或者代码。
jvm同步基于进入和退出,是基于管程对象实现的
用户线程和守护线程
用户线程:自定义线程,主线程结束了,用户线程还在运行,jvm存活
守护线程:没有用户线程了,都是守护线程,jvm结束
Lock和synchronized区别
-
Lock是一个接口,而synchronized是java中的关键字,synchronized是内置的语言实现
-
synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生,而Lock发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock需要在finally块中释放锁
-
Lock可以让等待锁的线程响应中断,而synchronized不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
-
通过Lock可以知道有没有成功获取锁,而synchronized无法办到
-
Lock可以提高多个线程进行读操作的效率。
注:在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈的时候(即大量线程同时竞争),此时Lock的性能远远优于synchronized。
线程通信间通信
比如定义1个数:为0时A线程+1,为1时B线程-1,否则等待
Lock实现
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author liuwenpu
* Description
* @version 1.0
* @date 2021/9/6 23:44
*/
public class LockSocket {
public static void main(String[] args) {
TestLock lock = new TestLock();
new Thread( () -> {
for (int i = 0; i < 10; i++) {
lock.increment();
}
},"A").start();
new Thread( () -> {
for (int i = 0; i < 10; i++) {
lock.decrement();
}
},"B").start();
}
}
/**
* 定义资源类
*/
class TestLock{
//定义可重入锁
private Lock lock = new ReentrantLock();
//绑定锁
private Condition condition =lock.newCondition();
//定义操作的值
private int index = 0;
/**
* 递增方法
*/
public void increment(){
lock.lock(); //线程锁
try{
while(index != 0){ //使用while替换if防止“虚假唤醒”
try {
condition.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index++;
System.out.println(Thread.currentThread().getName()+"::"+index);
condition.signalAll(); //唤醒其他线程操作
}finally {
//释放锁,定义在finally防止异常造成死锁
lock.unlock();
}
}
/**
* 递减方法
*/
public void decrement(){
lock.lock(); //线程锁
try{
while(index == 0){ //使用while替换if防止“虚假唤醒”
try {
condition.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
System.out.println(Thread.currentThread().getName()+"::"+index);
condition.signalAll();
}finally {
//释放锁,定义在finally防止异常造成死锁
lock.unlock();
}
}
}
synchronized实现
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author liuwenpu
* Description
* @version 1.0
* @date 2021/9/6 23:44
*/
public class LockSocket {
public static void main(String[] args) {
TestSynchronized lock = new TestSynchronized();
new Thread( () -> {
for (int i = 0; i < 10; i++) {
lock.increment();
}
},"A").start();
new Thread( () -> {
for (int i = 0; i < 10; i++) {
lock.decrement();
}
},"B").start();
}
}
/**
* 定义资源类
*/
class TestSynchronized{
//定义操作的值
private int index = 0;
/**
* 递增方法
*/
public synchronized void increment(){
while(index != 0){ //使用while替换if防止“虚假唤醒”
try {
this.wait(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index++;
System.out.println(Thread.currentThread().getName()+"::"+index);
this.notifyAll(); //唤醒其他线程操作
}
/**
* 递减方法
*/
public synchronized void decrement(){
while(index == 0){ //使用while替换if防止“虚假唤醒”
try {
this.wait(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
System.out.println(Thread.currentThread().getName()+"::"+index);
this.notifyAll();
}
}
线程定制化通信
案例
案例描述:A线程输出3次通知B线程,B线程输出6次通知C线程,C线程输出8次通知A线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author liuwenpu
* Description
* @version 1.0
* @date 2021/9/6 23:44
*/
public class LockSocket {
public static void main(String[] args) {
TestLock lock = new TestLock();
new Thread( () -> {
for (int i = 0; i < 5; i++) {
lock.test1(i);
}
},"A").start();
new Thread( () -> {
for (int i = 0; i < 5; i++) {
lock.test2(i);
}
},"B").start();
new Thread( () -> {
for (int i = 0; i < 5; i++) {
lock.test3(i);
}
},"C").start();
}
}
/**
* 定义资源类
*/
class TestLock{
//定义可重入锁
private Lock lock = new ReentrantLock();
//绑定锁
private Condition condition =lock.newCondition();
private Condition condition1 =lock.newCondition();
private Condition condition2 =lock.newCondition();
//定义操作的值
private int index = 1;
public void test1(int number){
lock.lock(); //线程锁
try{
while(index != 1){ //使用while替换if防止“虚假唤醒”
try {
condition.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 3; i++) {
System.out.println("第"+number+"次轮询:"+Thread.currentThread().getName()+"::"+i);
}
index=2;
condition1.signal(); //唤醒B线程操作
}finally {
//释放锁,定义在finally防止异常造成死锁
lock.unlock();
}
}
public void test2(int number){
lock.lock(); //线程锁
try{
while(index != 2){ //使用while替换if防止“虚假唤醒”
try {
condition1.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 6; i++) {
System.out.println("第"+number+"次轮询:"+Thread.currentThread().getName()+"::"+i);
}
index=3;
condition2.signal(); //唤醒B线程操作
}finally {
//释放锁,定义在finally防止异常造成死锁
lock.unlock();
}
}
public void test3(int number){
lock.lock(); //线程锁
try{
while(index != 3){ //使用while替换if防止“虚假唤醒”
try {
condition2.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 8; i++) {
System.out.println("第"+number+"次轮询:"+Thread.currentThread().getName()+"::"+i);
}
index=1;
condition.signal(); //唤醒B线程操作
}finally {
//释放锁,定义在finally防止异常造成死锁
lock.unlock();
}
}
}
测试结论
重点是定义不同的Condition对象,实现指定线程唤醒、等待操作
private Condition condition =lock.newCondition();
private Condition condition1 =lock.newCondition();
private Condition condition2 =lock.newCondition();
集合的安全性
ArrayList集合不安全案例
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) {
list.add(j);
}
System.out.println(list);
},"A"+i).start();
}
}
测试结论:当我们使用多个线程一起去操作ArrayList的时候,会出现java.util.ConcurrentModificationException异常。
ArrayList集合不安全解决方案
Vector解决
public static void main(String[] args) {
List list = new Vector();
//剩余代码和不安全案例一样
}
源码查看得知:就是把add方法添加了synchronized关键字,实现加锁保证一次只有一个线程去写。
Collections工具类解决
public static void main(String[] args) {
List list = Collections.synchronizedList(new ArrayList<>());
//剩余代码和不安全案例一样
}
源码查看得知:就是把add方法中添加synchronized块,实现加锁保证一次只有一个线程去写。
CopyOnWriteArrayList解决
public static void main(String[] args) {
List list = new CopyOnWriteArrayList<>();
//剩余代码和不安全案例一样
}
源码查看得知:通过ReentrantLock可重入锁的方式解决,写时复制技术,相对上面两个解决方案性能较优!
HashSet不安全案例及解决方案
不安全案例
public static void main(String[] args) {
Set set = new HashSet<>();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) {
set.add(j);
}
System.out.println(Thread.currentThread().getName()+"\t"+set);
},"A"+i).start();
}
}
解决方案
Set set = new CopyOnWriteArraySet();
//其余代码和不安全案例一致
源码查看得知:和list一样,写时复制技术,使用ReentrantLock可重入锁解决
HashMap不安全案例及解决方案
不安全案例
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int j = 0; j < 100; j++) {
map.put("--"+j,UUID.randomUUID().toString());
}
System.out.println(Thread.currentThread().getName()+"\t"+map);
},"A"+i).start();
}
}
解决方案
Map<String,String> map = new ConcurrentHashMap<>();
//其余代码和不安全案例一致
源码查看得知:使用的synchronized关键字解决
多线程锁
synchronized锁
-
synchronized加在非静态方法,锁的是this对象
-
synchronized加在静态方法,锁的是Class字节码对象
-
synchronized同步方法块,锁的是synchronized括号配置的对象
公平锁和非公平锁
//公平锁
Lock lock = new ReentrantLock(true);
//非公平锁
Lock lock = new ReentrantLock(false);
ReentrantLock默认无参构造函数就是非公平锁。
公平锁顾名思义就是会考虑到每个线程雨露均沾,假设3个线程,A线程能全部执行完但是为了公平还是会分摊时间切片给其他线程进行执行,效率相对于非公平锁较低,
非公平锁就是假设一个线程能干完的活不会考虑还有其他线程,直接全部执行完,导致其他线程饿死,效率较高。
可重入锁
synchronized和Lock默认都是可重入锁,比如一个锁中嵌入另外的锁代码,只要还是同一个锁,就可以直接进去
死锁
死锁案例
public static void main(String[] args) {
Object obj = new Object();
Object obj2 = new Object();
new Thread(() ->{
synchronized (obj){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了锁obj");
synchronized (obj2){
System.out.println(Thread.currentThread().getName()+"拿到了锁obj2");
}
}
},"A").start();
new Thread(() ->{
synchronized (obj2){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了锁obj2");
synchronized (obj){
System.out.println(Thread.currentThread().getName()+"拿到了锁obj");
}
}
},"B").start();
}
分析:A线程拿到了锁obj,这时候时间切片到了,轮到B线程执行,B线程拿到了obj2,想去拿obj的时候被A线程锁了未释放,就等待,然后A线程拿到线程执行权的时候,继续往下去拿obj2的锁,发现obj2被B线程锁了,导致拿不到,因此A和B互相等待对方的释放,从而产生死锁
死锁分析
使用jps查看目前的进程号
C:\Users\何大琴>jps -l
17424
9352 org.jetbrains.idea.maven.server.RemoteMavenServer36
15276 org.jetbrains.jps.cmdline.Launcher
15468 sun.tools.jps.Jps
17084 LockSocket
通过jstack根据进程号定位问题
以下的日志告诉我们,发现了一个java死锁,“Found one Java-level deadlock:”
C:\Users\何大琴>jstack 17084
Found one Java-level deadlock:
=============================
"B":
waiting to lock monitor 0x0000000018582388 (object 0x00000000d657ff08, a java.lang.Object),
which is held by "A"
"A":
waiting to lock monitor 0x0000000018584a08 (object 0x00000000d657ff18, a java.lang.Object),
which is held by "B"
Java stack information for the threads listed above:
===================================================
"B":
at LockSocket.lambda$main$1(LockSocket.java:42)
- waiting to lock <0x00000000d657ff08> (a java.lang.Object)
- locked <0x00000000d657ff18> (a java.lang.Object)
at LockSocket$$Lambda$2/1160264930.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"A":
at LockSocket.lambda$main$0(LockSocket.java:29)
- waiting to lock <0x00000000d657ff18> (a java.lang.Object)
- locked <0x00000000d657ff08> (a java.lang.Object)
at LockSocket$$Lambda$1/200006406.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
Callable接口实现多线程
Runnable接口和Callable区别
| 描述 | Runnable接口 | Callable接口 |
|---|---|---|
| 是否有返回值 | 否 | 是 |
| 是否抛出异常 | 否 | 是 |
| 实现接口需要实现的方法 | run | call |
语法
public class LockSocket {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//定义中间转换未来任务对象
FutureTask<String> ft = new FutureTask<>(new Test());
new Thread(ft).start();
//任务结束前一直输出
while (!ft.isDone()){
System.out.println("--------");
}
//获取返回值
System.out.println(ft.get());
}
}
class Test implements Callable<String>{
@Override
public String call() throws Exception {
return "呵呵。。。";
}
}
JUC强大辅助类
CountDownLatch减少计数
假设有个多线程在执行任务,我需要等他执行完然后主线程才能结束,某些情况可能主线程结束后,自定义线程才结束,这个时候可以使用CountDownLatch来解决,案例如下:
不加减少计数类的案例
public static void main(String[] args) throws ExecutionException, InterruptedException {
for (int i = 1; i < 8; i++) {
new Thread(() ->{
System.out.println(Thread.currentThread().getName()+"完成了任务!");
},i+"").start();
}
System.out.println("任务都完成了,主线程收场");
}
//实际上还有人任务没完成,但是主线程认为完成了,会收场
使用计数器解决
public static void main(String[] args) throws ExecutionException, InterruptedException {
//定义CountDownLatch对象及次数
CountDownLatch cdl = new CountDownLatch(7);
for (int i = 1; i < 8; i++) {
new Thread(() ->{
System.out.println(Thread.currentThread().getName()+"完成了任务!");
//每次完成任务后计数器减1
cdl.countDown();
},i+"").start();
}
//设置阻塞等待,在计数器大于0的情况下,一直等待
cdl.await();
System.out.println("任务都完成了,主线程收场");
}
//保证了必须所有线程的任务都结束,主线程才收场
CyclicBarrier循环栅栏
- 允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。
public static void main(String[] args) throws ExecutionException, InterruptedException {
int number = 7;
//表示只有7次都满足后才会输出定义好的语句
CyclicBarrier cb = new CyclicBarrier(number,() -> {
System.out.println("条件满足了");
});
for (int i = 1; i < 6; i++) {
new Thread(() ->{
System.out.println(Thread.currentThread().getName()+"完成了任务!");
try {
cb.await();
} catch (Exception e) {
e.printStackTrace();
}
},i+"").start();
}
}
Semaphore信号灯
- 一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个
acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象;Semaphore只保留可用数量的计数,并相应地执行。
以6辆车,3个车位举例
public static void main(String[] args) throws ExecutionException, InterruptedException {
//定义3个许可证
Semaphore s = new Semaphore(3);
//假设有6辆车
for (int i = 1; i < 7; i++) {
new Thread( () -> {
try{
//抢占资源
s.acquire();
System.out.println(Thread.currentThread().getName()+"--抢到了车位");
//设置车的随机停放时间,5秒内
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName()+"-离开了车位");
}catch(Exception e){
e.printStackTrace();
}finally {
//离开车位后释放资源
s.release();
}
},i+"").start();
}
}
读写锁
一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享。
优缺点
优点
读操作可以共享,提升性能
缺点
- 造成锁饥饿,一直读,没有写操作
- 读时候,不能写操作,只能读完之后,才可以写,写操作可以读
读写锁案例
以下案例测试,在我们同时去进行读写操作时,在没有写完的情况就会产生被读的情况,但是如果加了读写锁,就可以避免这种情况!
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author liuwenpu
* Description
* @version 1.0
* @date 2021/9/6 23:44
*/
public class LockSocket {
public static void main(String[] args) throws Exception {
TestReadWrite trw = new TestReadWrite();
for (int i = 1; i < 6; i++) {
int index = i;
new Thread(() ->{
trw.put(index+"",index+"");
},i+"").start();
}
for (int i = 1; i < 6; i++) {
int index = i;
new Thread(() ->{
trw.get(index+"");
},i+"").start();
}
}
}
class TestReadWrite{
private Map<String,String> map = new HashMap<>();
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key,String value){
rwLock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"进来写了");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写完了");
}catch(Exception e){
e.printStackTrace();
}finally {
rwLock.writeLock().unlock();
}
}
public Object get(String key){
rwLock.readLock().lock();
Object result = null;
try{
System.out.println(Thread.currentThread().getName()+"进来读了");
result = map.get(key);
System.out.println(Thread.currentThread().getName()+"读完了");
}catch(Exception e){
e.printStackTrace();
}finally {
rwLock.readLock().unlock();
}
return result;
}
}
读写锁降级案例
按照程序思维写的权限是大于读的,所以写锁可以降级为读锁,而读锁不能升级为写锁,以下通过案例说明
读写锁降级案例
以下案例得出结论,在写锁过程中,并没有释放,依然可以处理读锁的操作
public static void main(String[] args) throws Exception {
//创建读写锁
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
//获取读锁
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
//获取写锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
//写锁开启
writeLock.lock();
System.out.println("写锁开启了、、、");
//读锁开启
readLock.lock();
System.out.println("读锁开启了。。。。");
}
读写锁升级失败案例
以下案例得出结论,在读锁过程中,并没有释放的话,是不能进行写锁操作的,写锁会一直等待读锁释放才能进行写
public static void main(String[] args) throws Exception {
//创建读写锁
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
//获取读锁
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
//获取写锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
//读锁开启
readLock.lock();
System.out.println("读锁开启了。。。。");
//写锁开启
writeLock.lock();
System.out.println("写锁开启了、、、");
}
阻塞队列
概述
阻塞队列,顾名思义。首先它是一个队列,通过一个共享的队列,可以使得数据由队列的一端输入,从另一端输出。
- 当队列是空的时,从队列中获取元素的操作将会被阻塞
- 当队列是满的时,从队列中写入元素的操作就会被阻塞
为什么需要BlockingQueue
好处是我们不需要关系什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给我们一手包办了。
常见的BlockingQueue分类
注:以下为简易描述总结,具体描述看jdk文档
ArrayBlockingQueue(常用)
由数组结构组成的有界阻塞队列
LinkedBlockingQueue(常用)
由链表结构组成的有界(默认值大小为integer.MAX_VALUE)阻塞队列
DelayQueue
使用优先级队列实现的延迟无界阻塞队列
PriorityBlockingQueue
支持优先级排序的无界阻塞队列
SynchronousQueue
不存储元素的阻塞队列,即单个元素的队列
LinkedTransferQueue
由链表结构组成的无界阻塞队列
LinkedBlockingDeque
由链表组成的双向阻塞队列
BlockingQueue核心方法
| 方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
|---|---|---|---|---|
| 插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
| 移除 | remove() | poll() | take() | poll(time,unit) |
| 检查 | element() | peek() | 不可用 | 不可用 |
抛出异常
当队列满时,再往队列中add插入元素会抛出异常:java.lang.IllegalStateException: Queue full
当队列空时,再往队列中remove移除元素会抛出异常:java.util.NoSuchElementException
特殊值
插入方法,成功返回true,失败返回false
移除方法,成功返回出队列的元素,队列没有就返回null
阻塞
当阻塞队列满时,生产者继续往队列里put元素,队列会一直阻塞生产者线程直到put数据响应中断退出
当阻塞队列空时,消费者线程试图从队列take元素,队列会一直阻塞消费者线程直到队列有数据可用
超时
当阻塞队列满时,队列会阻塞生产者线程指定时间,超过指定时间生产者线程自动中断退出
当阻塞队列空时,队列会阻塞消费者线程指定时间,超过指定时间消费者线程自动中断退出
小小案例
public static void main(String[] args) throws Exception {
//指定队列界为3
BlockingQueue bq = new ArrayBlockingQueue(3);
bq.add("1");
bq.add("2");
bq.add("3");
bq.remove();
bq.remove();
bq.remove();
bq.remove();
}
ThreadPool线程池
为什么用线程池
线程池做的工作只是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。可以减少CPU切换。
线程池有以下特点
线程复用:控制最大并发数,管理线程。
- 降低资源损耗,通过重复利用已创建的线程降低线程创建和销毁造成的损耗
- 提高响应速度,当任务到达时,任务可以不需要等待线程创建就能立即执行
- 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的基本使用(了解)
public static void main(String[] args) throws Exception {
//创建固定大小的线程池
ExecutorService es = Executors.newFixedThreadPool(5);
//创建单例线程池
// ExecutorService es = Executors.newSingleThreadExecutor();
//创建可伸缩的线程池
// ExecutorService es = Executors.newCachedThreadPool();
for (int i = 1; i <= 10; i++) {
es.execute(new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t正在处理");
},i+""));
}
//关闭线程池
es.shutdown();
}
线程池7大参数
- corePoolSize:线程池中常驻核心线程数
- maximumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于1
- keepAliveTime:多余的空闲线程的存活时间,当前池中线程数量超过corePollSize时,当空闲时间达到keepAliveTime时,多余的线程会被销毁直到只剩下corePollSize个线程为止
- workQueue:任务队列,被提交但尚未被执行的任务
- threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认的即可
- handler:拒绝策略,表示当队列满了,并且工作线程大于等线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略。
- unit:keepAliveTime的单位
线程池底层工作原理
- 创建线程池后,开始等待请求
- 当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1:如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
2.2:如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
2.3:如果队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务
2.4:如果队列满了且正在运行的线程数量大于等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行
-
当一个线程完成任务时,它会从队列中取下一个任务来执行
-
当一个线程无事可做时且超过一定的时间(keepAliveTime),线程就会进行如下判断:
4.1:如果当前运行的线程数大于corePoolSize,那么这个线程将被停掉
4.2:所有线程池的所有任务完成后,它最终会伸缩到corePoolSize的大小。
自定义线程池(常用)
public static void main(String[] args) throws Exception {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS, //超时单位
new LinkedBlockingQueue<>(5), //阻塞队列大小
Executors.defaultThreadFactory(), //默认工程创建
new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略,更多看源码
);
for (int i = 0; i < 20; i++) {
tpe.execute(new Thread( () ->{
System.out.println(Thread.currentThread().getName()+"\t:正在处理。。。");
},i+""));
}
tpe.shutdown();
}
ForkJoinPool分支合并
算数合并案例如下:
import java.util.concurrent.*;
/**
* @author liuwenpu
* Description
* @version 1.0
* @date 2021/9/6 23:44
*/
public class ForkJoinPoolTest {
public static void main(String[] args) throws Exception {
MyTask my = new MyTask(0,1000);
//创建执行任务的分枝合并框架
ForkJoinPool fjp = new ForkJoinPool();
//提交计算
ForkJoinTask<Integer> result = fjp.submit(my);
//输出计算值
System.out.println(result.get());
//关闭
fjp.shutdown();
}
}
class MyTask extends RecursiveTask<Integer>{
/**
* 定义阈值,小于10的就不使用分支合并计算
*/
private final Integer yuzhi = 10;
//开始值
private int begin;
//结束值
private int end;
//返回值
private int result;
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
if(end-begin <= yuzhi){
for (int i = begin; i <=end ; i++) {
result+=i;
}
}else{
//取中间值计算
int zj = (end+begin)/2;
MyTask t1 = new MyTask(begin,zj);
MyTask t2 = new MyTask(zj+1,end);
t1.fork(); //执行
t2.fork(); //执行
//合并值
result = t1.join() + t2.join();
}
return result;
}
}
CompletableFuture异步回调
无返回值回调
public static void main(String[] args) throws Exception {
CompletableFuture<Void> cf = CompletableFuture.runAsync( () -> {
System.out.println(Thread.currentThread().getName()+"这是一个没有返回值的异步回调");
});
//加该语句会阻塞后面的执行,等待回调执行完成
System.out.println(cf.get());
System.out.println("主线程语句");
}
有返回值回调
public static void main(String[] args) throws Exception {
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync( () -> {
System.out.println(Thread.currentThread().getName()+"这是一个有返回值的异步回调");
// int a = 10/0;
return 1204;
});
//获取返回值
Integer integer = cf.whenComplete((t, u) -> {
//正确的返回值
System.out.println("t------->" + t);
//异常信息
System.out.println("u------->" + u);
}).exceptionally(t -> {
//出现异常走这里,输出异常信息并定义默认返回值
System.out.println("异常信息:" + t.getMessage());
return 444;
}).get();
System.out.println("主线程语句");
}

浙公网安备 33010602011771号