9.读写锁
ReadWriteLock接口,它只有一个实现类,即:ReentrantReadWriteLock类
ReadWriteLock维护一对关联的locks
,一个用于只读操作,一个用于写入。read lock可以由多个线程同时去读,而write lock只能同时由一个线程去写!
独占锁(写锁) 一次只能被一个线程占有
共享锁(度锁) 多个线程可以同时占有
/**
* ReadWriteLock
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(度锁) 多个线程可以同时占有
* 读写存在三种情况:
* 读-读 可以共存
* 读-写 不能共存
* 写-写 不能共存
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//写入
for(int i=1; i<=5; i++){
final int temp = i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
//读取
for(int i=1; i<=5; i++){
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存,缓存其实只有两个操作,一个用于往其中set,一个用于从其中get
* 因为存的时候要求只能由一个线程操作,而取的时候可以由多个线程操作,所以
* 下方使用ReadWriteLock
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
//存,写:存的时候要求只能由一个线程操作
public void put(String key, Object object){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key, object);
System.out.println(Thread.currentThread().getName()+"写入OK");
}
//取,读:取的时候可以由多个线程操作
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
}
}
//返回结果为:
3写入3
2写入2
5写入5
3写入OK
5写入OK
4写入4
4写入OK
2写入OK
3读取3
1写入1
1写入OK
5读取5
5读取OK
4读取4
4读取OK
1读取1
1读取OK
2读取2
2读取OK
3读取OK
//很明显,上述的返回结果是有误的,因为我们所要求的是写入的时候只能由一个线程来操作,即上述
//打印:3写入3 后,后续应该是打印: 3写入OK,但它被:2写入2 给插入了,即一个线程在进行写操作的时候,另外一个线程又来执行了写入操作!
使用ReentrantReadWriteLock解决上述问题:
/**
* ReadWriteLock
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
//写入
for(int i=1; i<=5; i++){
final int temp = i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
}
}
//加锁的缓存实现
class MyCacheLock {
private volatile Map<String, Object> map = new HashMap<>();
//读写锁:更加细粒度的控制
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//存:写入的时候,只希望同时只有一个线程写!
public void put(String key, Object object){
//因为是进行写操作,所以对写锁加锁。写和读的锁分离,实现更加细粒度的控制!
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key, object);
System.out.println(Thread.currentThread().getName()+"写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//取:所有人都可以进行读取!
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
//打印输出结果为:
4写入4
4写入OK
5写入5
5写入OK
2写入2
2写入OK
3写入3
3写入OK
1写入1
1写入OK
//每一个线程的写入到写入OK没有被其他线程插队!
10.阻塞队列
阻塞:停止等待
队列:一个队列,在写入的时候,如果队列满了,就必须阻塞等待。在从队列中取出内容的时候,如果队列为空则必须阻塞等待
阻塞队列:BlockingQueue是一个接口
BlockingQueue接口
什么情况下我们会使用阻塞队列:多线程、线程池
BlockingQueue接口实际上是和List、Set同级的接口,BlockingQueue的实现类有ArrayBlockingQueue、LinkedBlockingQueue等
学会使用队列
添加、移除
阻塞队列的四组API
1.抛出异常
2.不会抛出异常(有返回值)
3.阻塞等待
4.超时等待
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | 带参数offer()方法 |
移除 | remove() | poll() | take() | 带参数poll()方法 |
检测队首元素 | element() | peek() | - | - |
/**
* 抛出异常
*/
public static void test1(){
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println(blockingQueue.element()); //查看队首元素
//IllegalStateException: Queue full 抛出异常
//System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//java.util.NoSuchElementException 抛出异常
//System.out.println(blockingQueue.remove());
}
/**
* 有返回值,没有异常
*/
public static void test2(){
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("c")); // 返回false 不抛出异常
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll()); // 返回null 不抛出异常
}
/**
* 等待、阻塞(一直阻塞)
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//一直阻塞
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d"); //因为阻塞队列容量定义为3,当添加第4个元素时,会一直等待
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
}
/**
* 等待、阻塞(等待超时)
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
//blockingQueue.offer("d", 2, TimeUnit.SECONDS); //往队列中添加元素d,若队列是满的,则会延时等待,等待时间到了就会退出
System.out.println("================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
}
SynchronousQueue 同步队列
没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
存取操作为:put、take
/**
* 同步队列和其他的BlockingQueue是不一样的,因为SynchronousQueue
* 不存储元素,只要往其中put了一个元素,必须先从中take出来,才可以再put第二个元素
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
//同步队列
SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();
new Thread(()->{
try {
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 1");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 2");
synchronousQueue.put("3");
System.out.println(Thread.currentThread().getName()+"put 3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
11.线程池(重点)
线程池部分的内容:三大方法、7大参数、4种拒绝策略。搞懂这些,线程池差不多了!
池化技术:
程序的运行本质:占用系统资源!为了优化资源的使用,也就有了池化技术!
线程池、连接池、内存池、对象池!这些池的出现是因为单独资源的创建和销毁十分浪费系统资源,所以采用池化技术统一管理!
池化技术:事先准备好一些资源,当某个软件需要使用时,就去池子中拿,用完之后再还给池子!
线程池的好处:
1.降低资源的消耗
2.提高响应的速度
3.方便管理
线程复用,可以控制最大并发数、管理线程
线程池:创建的三大方法
/**
* Executors 工具类,该工具类中有三大方法
*
* 使用了线程池之后,要使用线程池来创建线程,而不再是new Thread方式
*/
public class Demo01 {
public static void main(String[] args) {
//ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
//ExecutorService threadPool = Executors.newFixedThreadPool(5); //固定线程池大小
ExecutorService threadPool = Executors.newCachedThreadPool(); //可伸缩的线程池
try {
for(int i=0; i<10; i++){
//使用了线程池之后,要使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,需要关线程池
threadPool.shutdown();
}
}
}
7大参数
源码分析:
//newSingleThreadExecutor()方法底层本质上是new一个ThreadPoolExecutor
//new ThreadPoolExecutor()时参数含义:核心线程池大小为1,最大核心线程池大小也为1
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
//newFixedThreadPool的底层也是ThreadPoolExecutor
//new ThreadPoolExecutor()时参数含义:核心线程池大小为给定参数,最大核心线程池大小也为给定参数
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//同理newCachedThreadPool底层也是ThreadPoolExecutor
//最大核心线程池大小也为Integer.MAX_VALUE,非常大,如果这么大的值去跑的话电脑一定会移除
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
本质:开启线程池调用的是ThreadPoolExecutor
接下来研究一下ThreadPoolExecutor的源码
//该方法有7个参数
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
对7大参数的理解:
手动创建一个线程池
//自定义线程池:工作中就是这么创建的,因为通过Executors方法创建的不安全
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(), // 线程工厂一般不会变化
new ThreadPoolExecutor.AbortPolicy());
4种拒绝策略
默认的终止策略是:AbortPolicy()
-> 相当于线程池已经开启了最大线程数,然后阻塞队列中也塞满了待执行的任务,当再有任务进入阻塞队列的时候就会抛出异常!
CallerRunsPolicy拒绝策略:即哪来的去哪里!
最大承载:Deque + max
所以当Deque + max < 待执行任务数 时,就会报rejectedExecution异常,下方代码中Deque + max = 3 + 5=8,所以当for循环有9个任务时,报了上述异常!
public class Demo01 {
public static void main(String[] args) {
//自定义线程池:工作中就是这么创建的,因为通过Executors方法创建的不安全
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(), // 线程工厂一般不会变化
new ThreadPoolExecutor.AbortPolicy());
//最大承载:Deque + max
try {
for(int i=0; i<9; i++){
//使用了线程池之后,要使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,需要关线程池
threadPool.shutdown();
}
}
}
AbortPolicy()
策略:银行满了,还有人进来,不处理这个人的,抛出异常
CallerRunsPolicy()
策略:哪来的去哪里,比如从main线程来的,那么银行满了,还有人进来,不处理这个人的,将这个人交给main线程去处理,相当于让你们公司帮你办银行卡,而不是让你去银行办银行卡!
DiscardPolicy()
策略:队列满了,丢掉任务,不会抛出异常!
DiscardOldestPolicy()
策略:队列满了,尝试去和最早的竞争,也不会抛出异常!
小结和拓展:maximumPoolSize如何设定
最大线程到底该如何定义:
1.CPU密集型
几核CPU就定义为多少,通过代码去获取服务器的核数。
//获取CPU的核数
System.out.println(Runtime.getRuntime().availableProcessors());
//所以可以用下述代码定义一个线程池,第二个参数直接用的CPU核数
ExecutorService threadPool = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(),
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(), // 线程工厂一般不会变化
new ThreadPoolExecutor.DiscardOldestPolicy());
2.IO密集型
大于核数即可!2倍的核数最宜!
12.四大函数式接口(必须掌握)
新时代程序员必须要掌握的几点:lambda表达式、链式编程、函数式接口、Stream流式计算=> 取代for循环的方法
函数式接口:即只有一个方法的接口
该接口的上方会有一个注解:@FunctionalInterface
比如:Runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Java中有超级多@FunctionalInterface,它可以简化编程模型,在新版本的框架底层大量使用!
如果不懂函数式接口的话,面试官随便问你个问题你可能都不懂,比如面试官问:foreach的参数是什么?
深入到forEach()方法的底层:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
发现它的参数为Consumer接口,它也是一个函数式接口:
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
并且forEach的参数是消费者类的函数时接口!
四大函数式接口:在java.util.function包下查看
代码测试:
Function函数式接口
package com.codeyu.function;
import java.util.function.Function;
/**
* Function 函数式接口,有一个输入参数,有一个输出
* 并且:只要是函数式接口,就可以用lambda表达式简化(因为实现的一定是那唯一一个方法)
*/
public class Demo01 {
public static void main(String[] args) {
//匿名内部类
// Function function = new Function<String, String>() {
// @Override
// public String apply(String s) {
// return s;
// }
// };
//写lambda表达式分如下几步:1.写一个()->{}
// 2.将参数丢入()中
// 3.然后函数体写在{}中即可!
Function function = (str)->{
return str;
};
System.out.println(function.apply("abc"));
}
}
断定型接口Predicate:有一个输入参数,返回值只能是布尔值!
package com.codeyu.function;
import java.util.function.Predicate;
/**
* 断定型接口:有一个输入参数,返回值只能是布尔值!
*/
public class Demo02 {
public static void main(String[] args) {
//功能:判断一个整型值是否大于0
// Predicate<Integer> predicate1 = new Predicate<Integer>(){
// @Override
// public boolean test(Integer num) {
// return num > 0;
// }
// };
//上述匿名内部类简化为下述的lambda表述形式:
Predicate<Integer> predicate = (Obj)->{ return Obj > 0; };
System.out.println(predicate.test(0));
}
}
消费型接口Consumer:只有输入,没有返回值
package com.codeyu.function;
import java.util.function.Consumer;
/**
* Consumer消费型接口:只有输入,没有返回值
*/
public class Demo03 {
public static void main(String[] args) {
Consumer<String> consumer = new Consumer<>() {
@Override
public void accept(String str) {
System.out.println(str);
}
};
Consumer<String> consumer1 = (str) -> { System.out.println(str); };
consumer.accept("123");
consumer1.accept("456");
}
}
供给型接口Supplier:没有参数,只有返回值
package com.codeyu.function;
import java.util.function.Supplier;
/**
* Supplier 供给型接口:没有参数,只有返回值
*/
public class Demo04 {
public static void main(String[] args) {
Supplier<String> supplier = new Supplier<>() {
@Override
public String get() {
return "Hello world!";
}
};
//lambda表达
Supplier<String> supplier1 = () -> {return "Hello world---!";};
System.out.println(supplier.get());
System.out.println(supplier1.get());
}
}
函数式接口的作用:
简化编程模型!
Java程序员必须要会的技术:泛型、枚举、反射、lambda表达式、链式编程、函数式接口、Stream流式计算
13.Stream流式计算
什么是流式计算?
大数据时代:存储+计算
用来存储东西的,如:集合、MySQL,它们本质就是存储东西的!
但是真正的计算操作应该交给流来操作!
流位于Java.util.stream包下。该包下都是与流相关的内容,以下主要介绍Stream
接口:
//User类定义
package com.codeyu.stream;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private int age;
}
//Test类
package com.codeyu.stream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
/**
* 现在有一道题目:要求一分钟内完成此题,只能用一行代码实现!
* 现在有5个user,赛选:
* 1.ID必须是偶数
* 2.年龄必须大于23岁
* 3.用户名转为大写字母
* 4.用户名字母倒着排序
* 5.只输出一个用户!
*
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(1, "a", 21);
User u2 = new User(2, "b", 22);
User u3 = new User(3, "c", 23);
User u4 = new User(4, "d", 24);
User u5 = new User(6, "e", 25);
//集合就是存储
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
//计算交给Stream流。下方也正好是链式编程!
list.stream()
.filter(user->{ return user.getId()%2==0; })//通过实现Predicate函数式接口的匿名内部类来实现排序
.filter(user->{return user.getAge()>23; })//通过实现Predicate函数式接口的匿名内部类来实现排序
.map(user -> {
//通过实现Function函数式接口的匿名内部类来实现将name字段转为大写
user.setName(user.getName().toUpperCase());
return user;
})
.sorted((o1, o2) -> {
//通过实现Comparator函数式接口的匿名内部类来实现排序
if(o1.getName().charAt(0) > o2.getName().charAt(0)){
return -1;
}
else if(o1.getName().charAt(0) < o2.getName().charAt(0)){
return 1;
}
else {
return 0;
}
})
.limit(1) //只输出一个用户数据
.forEach(System.out::println);
}
}
上方的排序部分参数是一个Comparator的函数式接口,其还可以有如下几种实现形式:
.sorted((uu1,uu2)-> uu1.getName().compareTo(uu2.getName()))
//因为String类型实现了Comparable接口,即实现了Comparable接口中的compareTo方法,所以上述比较name字段时,可以直接使用String类型的compareTo()方法。
值得注意的是:不管是实现Comparator函数式接口中的compare方法,还是实现Comparable接口中的compareTo()方法,都是第一个参数与第二个参数比较,如果第一个参数的某个值大于第二个参数,并且此时主动令其返回正值,则排序是增序排序,若此时主动令其返回负值,则排序是降序排序。看上方Test类中的测试方法:
//因为要实现降序,所以当第一个参数的name字段值大于第二个参数的name字段值时,主动让其返回负值,此时能实现将降序排序!
.sorted((o1, o2) -> {
//通过实现Comparator函数式接口的匿名内部类来实现排序
if(o1.getName().charAt(0) > o2.getName().charAt(0)){
return -1;
}
else if(o1.getName().charAt(0) < o2.getName().charAt(0)){
return 1;
}
else {
return 0;
}
})
14.ForkJoin
什么是ForkJoin?
ForkJoin 在JDK1.7出现,它主要是将大任务拆分成小任务再来执行,从而提高处理效率!
ForkJoin特点:工作窃取
如下图所示:比如两个线程A和B,假设A执行任务到1半的时候,B已经执行完毕了,那么它会去将A的任务拿过来执行,从而提高效率,这就是工作窃取!
ForkJoin中维护的是双端队列!
ForkJoin的操作
执行ForkJoin的操作,ForkJoin操作是通过ForkJoinPool
类对象来执行的,如下方法:
void execute(ForkJoinTask<?> task)
ForkJoin计算类:
package com.codeyu.forkjoin;
import java.util.concurrent.RecursiveTask;
/**
* 求和计算的任务
* 3000 6000(ForkJoin) 9000(Stream并行流)
* //如何使用ForkJoin
* //1.ForkJoinPool 通过它来执行
* //2.计算任务 forkJoinPool.execute(ForkJoinTask task)
* //3.计算类要继承ForkJoinTask
*
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
private Long threshold = 300000000L;
//计算方法
@Override
protected Long compute() {
//只要end-start大于临界值,我们就希望它去走分支合并,即ForkJoin
//临界值
if((end - start) > threshold){
//分支合并(ForkJoin操作) 递归
long middle = (start+end)/2; //中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork(); //拆分任务,把任务压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
task2.fork(); //拆分任务,把任务压入线程队列
//将二者计算结果合并
long res = task1.join() + task2.join();
return res;
}
else {
//否则的话,执行普通的计算方式,不走分支合并
Long sum = 0L;
for(Long i=start; i<=end; i++){
sum+=i;
}
return sum;
}
}
}
测试类:
package com.codeyu.forkjoin;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/**
* 3000 6000(ForkJoin) 9000(Stream并行流)
*/
public class Test {
public static void main(String[] args) throws Exception {
//test1(); //sum=500000000500000000 时间:11455
//test2(); //sum=500000000500000000 时间:9016
//test3(); //sum=500000000500000000 时间:924
//test3()所使用的stream式计算花费时间最少!效率提高十几倍
}
//普通
public static void test1(){
Long sum = 0L;
long start = System.currentTimeMillis();
for(Long i=0L; i<=1000000000; i++){
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));
}
//forkjoin
public static void test2() throws Exception {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get(); //此处get()方法会阻塞等待结果
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));
}
//stream并行流
public static void test3(){
long start = System.currentTimeMillis();
//stream并行流
Long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0,Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));
}
}
ForkJoin在大数据量的情况下才使用!小数据量没必要使用!
15.异步回调
Future
package com.codeyu.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* 异步回调:
* 执行成功 -> 成功回调
* 执行失败 -> 失败回调
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回之的异步回调。下方的Void是void的封装
// CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"runAsync=>void");
// });
//
// completableFuture.get(); //获取返回值,该get()方法会产生阻塞,所以最终"11111111"后打印
//
// System.out.println("11111111");
//====================下方代码是有返回值的异步回调=========================
//ajax有成功/失败的回调!
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
int i = 10/0;
return 1024; //成功时返回1024
});
System.out.println(completableFuture.whenCompleteAsync((t, u) -> {
System.out.println("t=>" + t); //正常的返回结果
System.out.println("u=>" + u); //错误信息:java.util.concurrent.CompletionException
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233; //失败时返回233
}).get());
/**
* success code 200
* error code 400 500
*
*/
}
}