Java并发编程
并发编程可以充分利用计算机的资源,把计算机的性能发挥到最大,可以最大程度节约成本,提高效率。
1、什么是高并发
并发vs并行的区别
并发concurrency: 多线程同时操作同一个资源,并不是真正的同时操作,而是交替操作,单核CPU的情况下,资源按时间分配给多个线程。
并行parallellism: 是真正的多个线程同时执行,多核CPU,每个线程使用一个CPU资源来运行。
并发编程描述的是一种使系统允许多个任务可以在重叠的时间段内执行设计结构,不是指多个任务在同一时间段内执行,而是指系统具备处理多个任务在同一时间内同时执行能力。
高并发是指我们设计的程序,可以支持海量任务的执行在时间段上重叠的情况。
高并发的标准:
-
QPS: 每秒响应的HTTP请求数量,QPS不是并发数
-
吞吐量:单位时间内处理的请求数,由QPS和并发数来决定
-
平均响应时间:系统对一个请求做出响应的平均时间。
QPS = 并发数 / 平均响应时间 -
并发用户数: 同时承载正常使用系统的用户人数。
互联网分布式架构设计,提高并发能力的方式: -
垂直扩展
-
水平扩展
垂直扩展
提升单机处理能力
1、提升单价硬件设备,增加CPU核数升级网卡、硬盘扩容、升级内存
2、提升单机的架构性能,使用Cache提高效率,使用异步请求来增加单服务吞吐量,NoSQL提升数据库访问能力
水平扩展
集群:一个厨师搞不定,多雇几个厨师一起炒菜,多个人干同一件事情
分布式:一件事拆分成多个步骤,由不同的人去完成
站点成扩展: Nginx反向代理,一个Tomcat跑不动,就是10个Tomcat去跑
服务层扩展:RPC框架实现远程调用,Spring Boot/Spring Cloud, Dubbo, 分布式架构,将业务逻辑拆分到不同的RPC Client, 各自完成对应的业务,如果某些业务并发量很大,增加新的RPC Client,就能扩展服务层的性能,做到理论上的无线高并发。
数据层的扩展:在数据量很大情况下,将原来的一台数据服务器,拆分成多台,以达到扩充系统性能的目的,主从复制,读写分离,分表分库。
JUC
JDK提供的一个工具包,专门用来帮助开发者完成Java并发编程。
进程和线程
Java默认的线程数是2个
- main主线程
- GC 垃圾回收机制
Java本身是无法开启线程,Java无法操作硬件,只能通过调用本地方法。C++编写的动态函数库。
Java中实现多线程有几种方式?
1、继承Thread类
2、实习Runnable接口
3、实现Callable接口
Callable和Runnable的区别在于Runnable的run方法没有返回值,Callable的Call方法没有返回值。
package com.xmm.juc;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @Description TODO
* @Author Xm
* @Date 2022/7/21 22:23
*/
public class Test {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
try {
String value = futureTask.get();
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("callable");
return "hello";
}
}
sleep 和 wait
sleep是让当前线程休眠,wait是让访问当前对象的线程休眠
sleep不会释放锁,wait会释放锁
synchronized锁的是什么
1、synchronized修饰非静太方法,锁定方法的调用者
2、synchronized修饰静态方法,锁定的是类
3、synchronized静态方法和实例方法同时存在,静态方法锁定的是类,实例方法锁定的是对象
Lock
JUC提供的一种锁机制,功能和synchronized类似,是对synchronized的升级,它是一个接口。
synchronized是通过JVM实现锁机制
ReentrantLock是通过JDK实现锁机制
重入锁:可以给同一个资源添加多把锁
synchronized自动释放锁
ReentrantLock需要手动解锁
synchronized完成买票
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "B").start();
}
}
class Ticket {
private Integer selNum = 0;
private Integer lastNum = 30;
public synchronized void sale() {
if (lastNum > 0) {
selNum++;
lastNum--;
try {
TimeUnit.MICROSECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + selNum + "张剩余," + lastNum);
}
}
}
ReentrantLock完成买票
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test2 {
public static void main(String[] args) {
Ticket2 ticket2 = new Ticket2();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket2.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket2.sale();
}
},"B").start();
}
}
class Ticket2{
private Integer saleNum = 0;
private Integer lastNum = 30;
private Lock lock = new ReentrantLock();
public void sale(){
lock.lock();
if(lastNum > 0){
saleNum++;
lastNum--;
try {
TimeUnit.MICROSECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖出了第"+saleNum+"张票,还剩"+lastNum+"张票");
}
lock.unlock();
}
}
》synchronized和Lock的区别
1、synchronized自动上锁,自动释放锁,Lock手动上锁
2、synchronized无法判断是否获取到了锁,Lock可以判断是否拿到了锁。
3、synchronized拿不到锁就会一直等待,Lock不一定会一直等待。
4、synchronized是Java关键字,Lock是接口
5、synchronized是非公平锁,Lock可以设置是否为公平锁。
公平锁:所谓公平就是 排队 不许插队 ,当前线程需要判断队列中是否有其他等待线程
非公平锁:不公平,可以插队,当锁没有被占用时,当前线程可以直接占用,而不需要判断当前队列中是否有等待线程。
实际开发中推荐使用Lock锁
ReentrantLock具备限时性的特点,可以判断某个线程在一定时间内能否获取到锁,使用tryLock方法,返回值时Boolean类型,true可以表示获取到锁,false表示无法获取到锁。
package com.xmm.reentrantlock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
TimeLock timeLock = new TimeLock();
new Thread(()->{
timeLock.getLock();
},"A").start();
new Thread(()->{
timeLock.getLock();
},"B").start();
}
}
class TimeLock {
private ReentrantLock lock = new ReentrantLock();
public void getLock() {
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "拿到了锁");
TimeUnit.SECONDS.sleep(5);
} else {
System.out.println(Thread.currentThread().getName() + "未拿到了锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(lock.isHeldByCurrentThread())
lock.unlock();
}
}
}
生产者消费者模式
synchronized下的
public class Test {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i = 0; i < 30; i++) {
date.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
date.decrement();
}
},"B").start();
}
}
class Date{
private Integer num = 0;
public synchronized void increment(){
while (num != 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
this.notify();
System.out.println(Thread.currentThread().getName()+"生产了"+num+"个汉堡");
}
public synchronized void decrement(){
while (num == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
this.notify();//唤醒
System.out.println(Thread.currentThread().getName()+"消费了"+num+"个汉堡");
}
}
是线程用sleep,非线程用wait()
必须使用while判断,不能用if,因为if会存在线程虚假唤醒的问题,虚假唤醒就是一些wait方法会在除了notify的其他情况被唤醒,不是真正的唤醒,使用while完成多重检测,避免这样问题。
使用LOCK方式
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test2 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 30; i++) {
data.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
data.decrement();
}
},"B").start();
}
}
class Data{
private Integer num = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment(){
lock.lock();
while (num != 0){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
condition.signal();//唤醒
System.out.println(Thread.currentThread().getName()+"生产了"+num+"汉堡");
lock.unlock();
}
public void decrement(){
lock.lock();
while(num == 0){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
condition.signal();
System.out.println(Thread.currentThread().getName()+"消费了"+num+"个汉堡");
lock.unlock();
}
}
使用Lock锁,就不能用wait和notify来暂停和唤醒线程,而应该使用Condition的await和singal来暂停和唤醒线程。
ConcurrentModificationException
并发访问异常
多线程 同时读写
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("a");//写
System.out.println(list);//读
},String.valueOf(i)).start();
}
}
}
解决方案
1、Vector 是线程安全的
2、Collections.synchronizedLsit
List<String> list = Collections.synchronizedList(new ArrayList<>());
3、JUC: CopyOnWriteArrayList<>()
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
public class Test2 {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("a");
System.out.println(list);
}).start();
}
}
}
CopOnWrite写时复制,当往一个容器添加元素的时候,不直接给容器添加,而是先将当前容器复制一份,向新的容器中添加数据,添加完成之后,再将原容器的引用指向新的容器。
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();
}
}
使用set set不能重复
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args)
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
final int temp = i;
new Thread(()->{
try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
set.add(String.valueOf(temp)+"a");//写
System.out.println(set);//读
},String.valueOf(i)).start();
}
}
}
Map使用
package com.xmm.demo5;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
/**
* @Description TODO
* @Author Xm
* @Date 2022/7/24 22:25
*/
public class Test {
public static void main(String[] args) {
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
final int temp = i;
new Thread(() -> {
try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(UUID.randomUUID().toString().substring(0,3),UUID.randomUUID().toString().substring(0,2));//写
System.out.println(map);//读
}, String.valueOf(i)).start();
}
}
}
JUC 工具类
CountDownLatch: 减法计数器
可以用来倒计时,当两个线程同时执行时,如果确保一个线程优先执行,可以使用计数器,当计数器清零的时,再让另一个线程执行
package com.xmm.demo5;
import java.util.concurrent.CountDownLatch;
/**
* @Description TODO
* @Author Xm
* @Date 2022/7/24 23:11
*/
public class test3 {
public static void main(String[] args) {
//创建一个 CountDownLatch
CountDownLatch countDownLatch = new CountDownLatch(100);
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("++++++++++Thread");
countDownLatch.countDown();
}
}).start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println("main=---------------------");
}
}
}
- counDown(): 计数器减一
- await(): 计数器停止,唤醒其他线程
- new CountDownLatch(100)、countDown()、await()必须配合起来使用,创建对象的时候赋值是多少,counDown()就执行多少次,否则计数器是没有清零的,计数器就不会停止,其他线程也无法唤醒,所以必须保证计数器清零,countDown()的调用次数必须大于构造函数的参数值。
CyclicBarrier: 加法计数器
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(100,()->{
System.out.println("放行");
});
for (int i = 0; i < 100; i++) {
final int temp = i;
new Thread(()->{
System.out.println("-->"+temp);
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
await():在其他线程试图唤醒计数器线程,当其他线程的执行次数达到计数器的临界值时,则唤醒计数器线程,并计数器是可以重复使用的,当计数器的线程执行完成一次之后,计数器自动清零,等待下一次执行。
new CyclicBarrier(30),for执行90次,则计数器的任务会执行3次。
Semaphore: 计数信号量
实际开发中主要使用其来完成限流操作,限制可以访问某些资源的线程数量。
Semaphore只有三个操作
- 初始化
- 获取许可
- 释放
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreTest {
public static void main(String[] args) {
//初始化
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 15; i++) {
new Thread(()->{
//获得许可
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"进店购物");
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName()+"出店");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
每个线程在执行的时候,首先需要去获取信号量,只有获取到资源才可以执行,执行完毕之后需要释放资源,留给下一个线程。
读写锁
接口ReentrantLock, 实现类是ReentrantReadWriteLock,可以多线程同时读,但是同一时间内只能有一个线程进行写入操作。
读写锁也是为了实现线程同步,只不过粒度更细,可以分给读和写的操作设置不同的锁。
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static void main(String[] args) {
Cache cache = new Cache();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
cache.write(temp,String.valueOf(temp));
}).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
cache.read(temp);
}).start();
}
}
}
class Cache{
private Map<Integer,String> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 写操作
*/
public void write(Integer key,String value){
readWriteLock.writeLock().lock();
System.out.println(key+"开始写入");
map.put(key,value);
System.out.println(key+"写入完毕");
readWriteLock.writeLock().unlock();
}
/**
* 读操作
*/
public void read(Integer key){
readWriteLock.readLock().lock();
System.out.println(key+"开始读取");
map.get(key);
System.out.println(key+"读取完毕");
readWriteLock.readLock().unlock();
}
}
写入锁也叫独占锁,只能被一个线程占用,读取锁也叫共享锁,多个线程可以同时占用。
线程池-
预先创建好一定数量的线程对象,存入缓冲池中,需要用的时候直接从缓冲池中取出,用完之后不要销毁,还回到缓存池中,为了提高资源的利用率。
线程池的具体设计思想
- 核心池的大小
- 线程池的最大容量
- 等待队列
- 拒绝策略
优势: - 提高线程的利用率
- 提高响应速度
- 便于统一管理线程对象
- 可以控制最大的并发数
1、线程池初始化的时候创建一定数量线程对象。
2、如果缓冲池中没有空闲的线程对象,则新来的任务进入等待队列。
3、如果缓冲池中没有空闲的线程对象,等待队列也已经填满,可以申请在创建一定数量的新线程对象,直到达到线程池的最大值,这时如果还有新的任务进来,只能选择拒绝。
无论哪种线程池,都是工具类Executors封装的,底层代码都一样,都是通过创建ThreadPoolExcecutor对象来完成线程池的构建。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
线程池启动的时候会按照核心池的数来创建初始化的线程对象 如2个。
开始分配任务,如果同时来了多个任务, 2个线程对象都被占用了,第三个以及之后的任务进入等待队列,当前有线程完成任务恢复空闲状态的时候,等待队列中的任务获取线程对象。
如果等待队列也沾满了,又3有新的的任务进来,需要取协调,让线程池在创建新的线程对象。
如果线程池已经达到最大上限,并且等待队列也沾满了,此时如果有新的任务进来,只能选择拒绝,并且需要根据拒绝策略来选择对应的方案。
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test2 {
public static void main(String[] args) {
//单例线程池
//ExecutorService executorService = Executors.newSingleThreadExecutor();
//数量固定线程池
//ExecutorService executorService = Executors.newFixedThreadPool(5);
//缓存线程池,线程池的线程实例数量随机,由电脑的配置决定
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int temp = i;
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+":"+temp);
});
}
executorService.shutdown();
}
}
ThreadPoolExecutor
直接实例化ThreadPoolExecutor,实现定制化的线程池,而不推荐使用Executors提供的封装好的方法,因为这种方式代码不够灵活,无法实现定制化。
ThreadPoolExecutor核心参数 一共有7个
- corePoolSize: 核心池大小
- maximumPoolSize:线程池的最大容量,他决定了线程池容量上限,corePoolSize就是线程池的大小,maximumPoolSize是一种补救措施,任务量突然增大的时候的一种补救措施。
- keepAliveTime:线程对象的存活时间(在没有任务可执行的情况下),必须是线程池中的数量大于corePoolSize,才会生效
- TimeUint:线程对象存活时间单位
- BlockingQueue:等待队列,存储等待执行的任务
- ThreadFactory:线程工厂,用来创建线程对象
- RejectedExecutionHandler:拒绝策略
拒绝策略
1、AbortPolicy:直接抛出异常
2、DiscardPolicy:放弃任务,不抛出异常
2、DiscardOldestPolicy:尝试与等待队列中最前面的任务去竞争,不抛出异常
4、CallerRunsPolicy:谁调用谁处理
单例
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
固定
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
缓存线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
自定义线程池
import java.util.concurrent.*;
public class Test3 {
public static void main(String[] args) {
ExecutorService executorService = null;
try{
/**
* 自己写7大参数,完全定制化
* Core 2
* max 3
* Queue 2
*/
executorService = new ThreadPoolExecutor(
2,
3,
1L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
for (int i = 0; i < 5; i++) {
executorService.execute(()->{
try {
TimeUnit.MICROSECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===》》正在办业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
1、AbortPolicy:直接抛出异常
new ThreadPoolExecutor.AbortPolicy()
2、CallerRunsPolicy:谁调用谁处理
new ThreadPoolExecutor.CallerRunsPolicy()
3、DiscardOldestPolicy:尝试与等待队列中最前面的任务去竞争,不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy()
4、DiscardPolicy:放弃任务,不抛出异常
new ThreadPoolExecutor.DiscardPolicy()
线程池三大考点
1、Executors工具类的3中实现
单例线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
数量固定线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
缓存线程池,线程池的线程实例数量随机,由电脑的配置决定
ExecutorService executorService = Executors.newCachedThreadPool();
2、7个参数
- corePoolSize: 核心池大小
- maximumPoolSize:线程池的最大容量
- keepAliveTime:线程对象的存活时间(在没有任务可执行的情况下),必须是线程池中的数量大于corePoolSize,才会生效
- TimeUint:存活时间单位
- BlockingQueue:等待队列,存储等待执行的任务
- ThreadFactory:线程工厂,用来创建线程对象
- RejectedExecutionHandler:拒绝策略
3、4种拒绝策略
1、AbortPolicy:直接抛出异常
2、DiscardPolicy:放弃任务,不抛出异常
3、DiscardOldestPolicy:尝试与等待队列中最前面的任务去竞争,不抛出异常
4、CallerRunsPolicy:谁调用谁处理
线程池 workQueue
一个阻塞队列,用来存储等待执行的任务,常用的阻塞队列有以下几种:
- ArrayBlockingQueue: 基于数组的先进先出队列,创建时必须指定大小。
- LinkedBlockingQueue: 基于链表的先进先出队列,创建时可以不指定大小,默认值是Integer.MAX_VALUE
- SynchronousQueue: 不会保存提交的任务,而是直接新建一个线程来执行新来的任务。
- PriorityBlockingQueue: 具有优先级的阻塞队列。
ForkJoin框架
ForkJoin是JDK1.7后发布的多线程并发处理框架,功能上和JUC类似,JUC更多时候是使用单个类完成操作,ForkJoin使用多个类同时完成某项工作,处理上比JUC更加丰富,实际开发中使用的场景并不是很多,互联网公司真正有高并发需求的情况才会使用。
本质上是对线程池的一种补充,对线程池功能的一种扩展,基于线程池,它核心思想就是将一个大型任务拆分成很多个小任务,分别执行,最终将小任务的结果进行汇总,生成最终的结果。
本质就是把一个线程的任务拆分成多个小任务,然后由多个线程并发执行,最终将结果进行汇总。
比如A B两个线程同时执行,A的任务比较多,B的任务比较少,所以B就先执行完毕,这时候B去帮助A完成任务(将A的一部分任务拿过来替A执行,执行完毕之后再把结果进行汇总),从而提高效率。
工作窃取
ForkJoin框架,核心是两个类
- ForkJoinTask (描述任务)
- ForkJoinPool(线程池) 提供多线程并发工作窃取
使用ForkJoinTask最重要的就是要搞清除如何拆分任务,这里用的递归思想。
1、需要使用创建ForkJoinTask任务,ForJoinTask是一个抽象类,不能直接创建ForkJoinTask的实例化对象,开发者需要自定义一个类,继承ForkJoinTask的子类RecursiveTask,
Recursive就是递归的意思,该类就是提供了实现递归的功能。
10亿求和
package com.xmm.threadpool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* 10亿求和
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start = 1L;
private Long end = 10_0000_0000L ;
private Long temp = 100_0000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if((end-start)<temp){
Long sum = 0L;
for(Long i = start; i <= end ; i++){
sum +=i;
}
return sum;
}else {
Long avg = (start+end)/2;
ForkJoinDemo task1 = new ForkJoinDemo(start,avg);
task1.fork();
ForkJoinDemo task2 = new ForkJoinDemo(avg,end);
task2.fork();
return task1.join()+task2.join();
}
}
}
Test
package com.xmm.threadpool;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
public class Test {
public static void main(String[] args) {
Long startTime = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L,10_0000_0000L);
forkJoinPool.execute(task);
Long sum = 0L;
try {
sum = task.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Long endTime = System.currentTimeMillis();
System.out.println(sum+",共耗时:"+(endTime-startTime));
}
}
Volatile 关键字
Volatile是JVM提供的轻量级同步机制,可见性,主内存对线程可见。
package com.xmm.vola;
import java.util.concurrent.TimeUnit;
public class Test {
private static volatile int num = 0;
public static void main(String[] args) {
/**
*循环
*/
new Thread(()->{
while (num == 0){
//System.out.println("----Thread---");
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
一个线程执行完成任务之后,会把变量存回到主内存中,并且从主内存中读取当前最新的值,如果是一个空的任务,则不会重新读取主内存中的值。
package com.xmm.vola;
import java.util.concurrent.TimeUnit;
public class Test {
private static int num = 0;
public static void main(String[] args) {
/**
*循环
*/
new Thread(()->{
while (num == 0){
System.out.println("----Thread---");
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}