持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情
JUC学习笔记(二)
JUC学习笔记的第二部分主要记录了集合类的安全问题、Callable接口、JUC的三大辅助类、读写锁、阻塞队列、线程池等知识点
集合类安全问题
List不安全
List并发测试
可能会报错:java.util.ConcurrentModificationException 并发修改异常。
解决方案
1、使用Vector,Vector默认是安全的,通过synchronized同步方法实现,效率较低,比较古老(不推荐),在ArrayList之前就有了, 说明官方并不想使用Vector来解决ArrayList导致的并发问题,而是引入新的解决方式。
2、使用Collections工具类提供的同步转换方法:List<String> list = Collections.synchronizedList(new ArrayList<>());
3、使用JUC下的线程安全集合:List<String> list = new CopyOnWriteArrayList<>();效率比Vecotr高
代码示例:
public class ListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// List<String> list = Collections.synchronizedList(new ArrayList<>());
// CopyOnWrite 写入时复制 存在多个线程时,读取时是固定的,写入时,需要先复制之前的内容给写入者,再进行统一写入,保证线程安全
// List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
// 并发测试结束
}
}
Set不安全
Set并发测试
可能会报错:java.util.ConcurrentModificationException 并发修改异常
解决方案
1、使用Collections工具类提供的同步转换方法:Set<String> set = Collections.synchronizedSet(new HashSet<>());
2、使用JUC下的线程安全集合:Set<String> set = new CopyOnWriteArraySet<>();
代码示例:
public class SetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
// 并发测试结束
}
}
HashMap不安全
Map并发测试
可能会报错:java.util.ConcurrentModificationException 并发修改异常
解决方案
1、使用Collections工具类提供的同步转换方法:Map<String, String> hashMap = Collections.synchronizedMap(new HashMap<>());
2、使用JUC下的线程安全集合:Map<String, String> hashMap = new ConcurrentHashMap<>();
代码示例:
public class HashMapTest {
public static void main(String[] args) {
Map<String, String> hashMap = new HashMap<>();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
hashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(hashMap);
}, String.valueOf(i)).start();
}
}
}
Callable接口
Callable接口可以有返回值,可以抛出异常,方法是call;
new Thread()无法直接调用实现Callable接口的对象,需要进行一个转换才可以(一个适配器)FutureTask类,该类实现了Runnable方法,可以通过new Thread(Runnable e) 方式来传入线程对象。查看源码,FutureTask可以接收一个Callable接口类型的参数,从而实现Callable接口与new Thread()关联起来。
代码示例:
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 定义一个FutureTask,这里会根据lamda表达式自动识别泛型的类型
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "线程" + "执行了call方法");
return "Callable";
});
new Thread(futureTask, "A").start();
// 如果多一个线程B,只有第一次A才会执行,B不执行,这里只会输出一个call
// FutureTask有一个state,state为NEW时才能保证Callable的可见性,A执行过依次之后,state会改变,Callable将不可见,B不执行
new Thread(futureTask, "B").start();
// 获得Callable接口call方法的返回值,这个get方法可能会被阻塞(比如call方法中有延迟),通常放在代码最后
System.out.println(futureTask.get());
}
}
JUC的三大辅助类
CountDownLatch、CyclicBarrier、Semaphore三大辅助类,都是用来计数的
CountDownLatch
辅助计时,主要是countDown方法和await方法配合使用,可以查看源码中的使用示例
代码示例:
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
// 定义一个计数器(源码中有使用示例),倒计时,并初始化count
CountDownLatch countDownLatch = new CountDownLatch(6);
// 开启6个线程
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程执行了");
// 计数器减1,这个方法不会被阻塞
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
// 直到计数器为0,才继续向下执行,否则一直等待
countDownLatch.await();
// 利用倒计时,这里所有线程执行完毕之后才会输出
System.out.println("所有线程执行完毕");
}
}
CyclicBarrier
源码中有使用示例,本质还是减法计数器
关键字:公共屏障点,就类似与CountDownLatch的count
代码示例:
public class CyclicBarrierTest {
public static void main(String[] args) {
/**
* 集7颗龙珠召唤神龙
*/
// 定义一个计数器,并初始化参数,第一个为公共屏障点的值
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙");
});
for (int i = 1; i <= 7; i++) {
// lamda表达式中不能调用外面的普通变量,需要是一个final变量,这里不能访问i,使用一个临时变量finalI,这里会自动添加final
int finalI = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集第" + finalI + "龙珠");
try {
// 直到达到公共屏障点之后才会继续向下执行,这里CyclicBarrier的底层实现是--count,和CountDownLatch类似,本质还是减法计数器
// 这个方法要放在run()方法里面执行,和CountDownLatch.await()不同
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
Semaphore
计数信号量。 从概念上讲,信号量维护一组许可。 如有必要,每个acquire块直到许可可用,然后获取它。 每个release增加一个许可,可能会释放一个阻塞的收单方。 但是,没有使用实际的许可对象; Semaphore只是计算可用的数量并相应地采取行动。 信号量通常用于限制可以访问某些(物理或逻辑)资源的线程数。
代码示例:
public class SemaphoreTest {
public static void main(String[] args) {
/**
* 抢车位模拟,有限资源,多个线程,限流时使用
*/
// 定义一个信号量,车位数量,限流
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
// 获得资源,得到车位
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
// 停留2秒
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放资源,车子离开,让出车位
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
ReadWriteLock - 读写锁
读锁:共享锁 写锁:独享锁、排他锁
某一时刻可以别多个线程同时读,但某一时刻只能被一个线程写。 3种情况:1、读-读 可以共存;2、读—-写 不能共存;3、写-写 不能共存
存在问题: 写入中间会出现插入现象,比如1插入未成功,同时又插入5,会出现混乱,这里读写的先后顺序不重要
资源类:
/**
* 如果使用synchronized关键字,则不能实现多个读一个写,synchronized锁的是对象(非静态同步方法时)或者类(Class,静态同步方法时)
*/
public class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
// 存,写,某一时刻只能有一个线程写
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入OK");
}
// 取,读,某一时刻可以有多个线程读
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "读取" + key);
System.out.println(map.get(key));
System.out.println(Thread.currentThread().getName() + "读取OK");
}
}
测试类:
public class ReadWriteLockTest {
public static void main(String[] args) {
// 资源类
MyCache myCache = new MyCache();
// 开启多条线程测试,写
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(() -> {
myCache.put(finalI + "", finalI + "");
}, String.valueOf(i)).start();
}
// 开启多条线程测试,读
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(() -> {
myCache.get(finalI + "");
}, String.valueOf(i)).start();
}
}
}
输出内容如下:
输出:2写入2、5写入5、0写入0、1写入1、4写入4、3写入3、3写入OK、2写入OK、0写入OK、1写入OK、5写入OK、4写入OK
解决方法: 给资源类添加读写锁,写入未完成不会再有其他写入插队
修改后的资源类:
/**
* 如果使用synchronized关键字,则不能实现多个读一个写,synchronized锁的是对象(非静态同步方法时)或者类(Class,静态同步方法时)
*/
public class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
// 定义一个读写锁,颗粒度更细,锁的内容仅是lock和unlock包围的代码块
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 存,写,某一时刻只能有一个线程写
public void put(String key, Object value) {
// 添加写锁,并上锁
readWriteLock.writeLock().lock();
try {
// 业务处理代码
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
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);
System.out.println(map.get(key));
System.out.println(Thread.currentThread().getName() + "读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
readWriteLock.readLock().unlock();
}
}
}
阻塞队列——BlockingQueue
是Collection下的Queue的一个实现类,本质上还是属于集合
阻塞
写入时,如果队列满则需要阻塞,等待有空余位置之后才能继续写入 读取时,如果对空则需要阻塞,等待队列中有元素之后才能读取
阻塞队列
必须设置队列大小
实现类如图:
应用场景
多线程并发处理、线程池
四组API
对队列进行添加、删除、查队首等操作时,采用不同的方式会有以下四种情况:
1、抛出异常 代码示例:
/**
* 报异常,这里使用的是add和remove方法,element查看队首元素
*/
public static void expectionTest(){
// 定义一个由数组支持的阻塞队列
BlockingQueue BlockingQueue=new ArrayBlockingQueue<>(2);
// 添加元素
BlockingQueue.add("a");
BlockingQueue.add("b");
// 队列大小之外额外添加元素,会报异常:IllegalStateException: Queue full 队满
// BlockingQueue.add("c");
// 查看队首元素
System.out.println(BlockingQueue.element());
// 删除队首元素
BlockingQueue.remove();
BlockingQueue.remove();
// 对空之后再删除,报异常:NoSuchElementException 队空,没有元素
// BlockingQueue.remove();
}
2、不抛出异常 代码示例:
/**
* 不抛异常,这里使用的是offer(添加元素),poll(弹出元素),peek查看队首元素
*/
public static void noExpectionTest(){
// 定义一个由数组支持的阻塞队列
BlockingQueue blockingQueue=new ArrayBlockingQueue<>(2);
// 添加元素
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
// 队列大小之外额外添加元素,会返回false,但不抛出异常
System.out.println(blockingQueue.offer("c"));
// 查看队首元素
System.out.println("队首元素:"+blockingQueue.peek());
// 删除元素
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// 为空后继续删除元素,返回null值,但是不会抛出异常
System.out.println(blockingQueue.poll());
}
3、阻塞等待
一直阻塞
代码示例:
/**
* 阻塞等待,这里用的是put(放),take(取)
*/
public static void blockWaitTest(){
BlockingQueue blockingQueue=new ArrayBlockingQueue<>(2);
try{
// 添加元素
blockingQueue.put("a");
blockingQueue.put("b");
// 队满之后继续添加元素,则会持续等待,不报错也没有返回值
// blockingQueue.put("c");
// 取出元素
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
// 队空之后继续取出元素,则会阻塞,持续等待
System.out.println(blockingQueue.take());
}catch(InterruptedException e){
e.printStackTrace();
}
}
4、超时等待
等待过一段时间之后,不再等待
代码示例:
/**
* 超时等待,这里使用的是携带参数的offer(value,时长数值,时长单位)和poll
*/
public static void timeoutWaitTest(){
BlockingQueue blockingQueue=new ArrayBlockingQueue<>(2);
try{
// 添加元素,offer的三个参数分别为:要放进队列的值,超时时长数值,超时时长单位(时、分、秒等)
System.out.println(blockingQueue.offer("a",2,TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("b",2,TimeUnit.SECONDS));
// 额外插入,超时2秒之后结束等待,上面可以不添加超时等待的参数,但如果要超出队列大小,使用超市等待策略就要添加参数
System.out.println(blockingQueue.offer("c",2,TimeUnit.SECONDS));
// 取元素
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
// 队空时取元素,如果使用超市等待策略,则需要添加参数:时长数值,时长单位
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}catch(InterruptedException e){
e.printStackTrace();
}
}
同步队列——SynchronousQueue
不需要设置队列大小,放进去一个元素必须取出来才能继续存放元素,不能连续两次存操作
代码示例:
public class SynchronousQueueTest {
public static void main(String[] args) {
// 定义一个同步队列
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
// 存元素线程
new Thread(() -> {
try {
// 存放元素
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "存放了1");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "存放了2");
synchronousQueue.put("3");
System.out.println(Thread.currentThread().getName() + "存放了3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "PUT").start();
// 取元素线程
new Thread(() -> {
try {
// 模拟延时
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "取出来" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "取出来" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "取出来" + synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "TAKE").start();
}
}
线程池(重点)
池化技术
能够优化资源的使用,事先准备好资源放到池子里,随用随取,用完就放回池子里,比如有数据库连接池(减少数据库连接开关浪费资源)、线程池
线程池
3大方法、7大参数、4中拒绝策略
好处:
- 降低资源消耗,频繁启动或杀死线程浪费CPU资源
- 提高响应速度,使用池子中已经存在的线程提高响应速度
- 方便管理多个线程
线程复用,可以控制最大并发数、管理线程
3大方法
使用Executors创建线程池的3大方法,三大方法的本质都是通过ThreadPoolExecutor构造线程池对象
/**
* Executors可以认为是一个工具类,用于创建线程池,但阿里Java开发规范不建议使用这种方式创建
* 推荐使用底层的ThreadPoolExecutor进行创建,能够更好的控制各个参数
*/
public class ExecutorTest {
public static void main(String[] args) {
// 创建一个只有一条线程的线程池
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 创建一个固定大小的线程池,大小由传入的参数限定
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 创建一个可伸缩大小的线程池,和CPU性能有关,如果池中有空闲线程则从池中拿,若没有则创建新的线程并放入池中
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 50; i++) {
// 启动线程,execute()方法中传入的依然是一个Runnable接口
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "线程OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 确保程序运行完毕(不再使用线程池)之后,关闭线程池
threadPool.shutdown();
}
}
}
7大参数
7大参数就是ThreadPoolExecutor构造方法的7个参数
ThreadPoolExecutor源码:
/**
* 使用给定的初始参数创建一个新的ThreadPoolExecutor 。
* 参数:
* corePoolSize – 要保留在池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
* maximumPoolSize – 池中允许的最大线程数
* keepAliveTime – 当线程数大于核心数时,这是多余空闲线程在终止前等待新任务的最长时间。
* unit – keepAliveTime参数的时间单位
* workQueue – 用于在执行任务之前保存任务的队列。 这个队列将只保存execute方法提交的Runnable任务。
* threadFactory – 执行程序创建新线程时使用的工厂
* handler – 执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量
* 抛出:
* IllegalArgumentException – 如果以下情况之一成立: corePoolSize < 0 keepAliveTime < 0 maximumPoolSize <= 0 maximumPoolSize <
corePoolSize
* NullPointerException – 如果workQueue或threadFactory或handler为 null
*/
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;
}
代码示例:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
// 创建一个自定义线程池
ExecutorService threadPool = new ThreadPoolExecutor(
// 要保留在池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
2,
// 线程池中的最大线程数哦,当需要的线程数目大于(阻塞队列大小+核心线程数)之后,线程池的大小会逐步变大,直到等于最大线程数
5,
// 除了核心线程之外其他线程最大空闲时长,超过这个时长则关掉其他线程
2,
// 时长单位
TimeUnit.SECONDS,
// 阻塞队列大小,用于存放排队的线程
new ArrayBlockingQueue<>(3),
// 阻塞之后的策略,当阻塞队列满,且线程池大小达到最大且所有线程都在使用,会采用给定的策略,ThreadPoolExecutor.AbortPolicy策略会抛出异常
new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 0; i < 10; i++) {
// 启动线程,execute()方法中传入的依然是一个Runnable接口
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "线程OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 确保程序运行完毕(不再使用线程池)之后,关闭线程池
threadPool.shutdown();
}
}
}
4大策略
就是ThreadPoolExecutor的最后一个参数,发生阻塞时采取的策略
ThreadPoolExecutor.CallerRunsPolicy():发生阻塞的任务从哪个线程来,回到哪个线程,不抛出异常 ThreadPoolExecutor.AbortPolicy():发生阻塞的任务不处理,抛出异常 ThreadPoolExecutor. DiscardPolicy():会丢掉发生阻塞的任务,不会抛出异常 ThreadPoolExecutor.DiscardOldestPolicy() :发生阻塞的任务会和阻塞队列中的最早的任务竞争,如果成功则进入阻塞队列等待,不抛出异常
线程池最大线程数的设置
** 参考:**blog.csdn.net/weixin_4212…
1、cpu密集型,多是运算型应用,io操作较少,主要消耗cpu资源,此时若开启多个线程,容易造成频繁的cpu上线文切换,增加额外时间消耗,顾线程数=cpu核心数+/-1比较合适。
2、io密集型,读写比较频繁,考虑阻塞、非阻塞、同步、异步等Io操作,CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度),增加线程数量可以充分利用cpu空闲时间片段 ,提高程序想用速率。网上对其线程池核心线程数大小有两种说法,第一种,线程数=cpu核数/(1-阻塞系数),阻塞系数大小范围为0.8-0.9,第二种,线程数=(线程等待时间+线程cpu时间)/线程cpu时间* cpu核数。其实仔细观察不难发现,阻塞系数和线程等待时间有某种关系。
四大函数式接口
函数式接口
参考:www.cnblogs.com/runningTurt…
只有一个抽象方法(可以有其他方法)的接口,比如Runnable接口
接口不会把其当作是抽象方法,从而符合函数式接口的定义。
- 所接口中所定义的方法式默认方法,使用default修饰,有其默认实现。
- 方法是静态方法,因为静态方法不能是抽象方法,而是一个已经实现了的方法。
- 方法是继承来自 Object 类的public方法,因为任何一个接口或类都继承了 Object 的方法 共同特点:都已实现
public interface Runnable {
/**
* 当使用实现接口Runnable的对象创建线程时,启动线程会导致在单独执行的线程中调用对象的run方法。
* 方法run的一般约定是它可以采取任何行动。
*/
public abstract void run();
}
java.util.function下有大量的函数式接口
四个函数式接口:
四个基本的函数式接口,非基础类型:Consumer、Function、Predicate、Supplier
Function函数型接口
给定泛型,第一个泛型为apply方法参数类型,第二个泛型为apply方法的返回值类型
匿名内部类使用函数式接口
public class FunctionDemo {
public static void main(String[] args) {
// 匿名内部类的方式使用函数式接口,可以用于自定义工具类
Function function = new Function<String, String>() {
@Override
public String apply(String o) {
return o;
}
};
System.out.println(function.apply("ZHANG"));
}
}
lamda表达式简化使用函数式接口
public class FunctionDemo {
public static void main(String[] args) {
Function<String, String> function = (s) -> {
return s;
};
System.out.println(function.apply("ZAHNG"));
}
}
Predicate断定型函数式接口
public class PredicateTest {
public static void main(String[] args) {
// lamda表达式实现断定型函数式接口
Predicate<String> predicate = (str) -> {
System.out.println(str);
return false;
};
System.out.println(predicate.test("ZHANG"));
}
}
Consumer消费型函数式接口
/**
* Consumer接口只有输入没有输出
*/
public class ConsumerTest {
public static void main(String[] args) {
Consumer<String> consumer = (str) -> {
System.out.println(str);
};
consumer.accept("ZHANG");
}
}
Supplier供给型函数式接口
/**
* Supplier只有输出没有输入
*/
public class SupplierTest {
public static void main(String[] args) {
Supplier<String> supplier = () -> {
return "ZHANG";
};
System.out.println(supplier.get());
}
}
Stream流
程序:存储+计算
集合用来存储,计算交给流
代码示例:
public class StreamTest {
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);
// 存储数据
List<User> list = Arrays.asList(u1, u2, u3, u4);
// 使用流进行计算
// lamda表达式、链式编程、函数式接口、方法引用的组合使用
list.stream()
// 过滤得到u的id取值能被2整除的用户,就是得到能使filter的参数Predicate接口返回值为真的用户
.filter((u) -> {
return u.getId() % 2 == 0;
})
.filter((u) -> {
return u.getAge() > 21;
})
// map中传入的接口是Function接口,修改输入值并返回指定类型的返回值。输入为流中User类型的u,返回u的名字的大写,并使用返回的名字替换流中的u
.map((u) -> {
return u.getName().toUpperCase();
})
// 输出
.forEach(System.out::println);
}
}
ForkJoin
在JDK1.7引入,用于处理大量数据,主要思想:大任务拆分为多个小任务,然后再把每个小任务的计算结果合并最终得到大任务的结果
特点:工作窃取,维护的资源是双端队列,一个线程执行完任务之后会帮另外一个线程执行任务
异步回调
类似与Ajax的异步通信技术,这里是线程之间,Ajax是客户端与服务器之间
Future
参考链接:blog.csdn.net/qq_29051413…
Future 接口在 Java 5 中被引入,设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。在Future 中触发那些潜在耗时的操作把调用线程解放出来(例如调用get方法),让它能继续执行其他有价值的工作,不需要等待耗时的操作完成。
如果已经运行到没有异步操作的结果就无法继续进行时,可以调用它的get方法去获取操作结果。如果操作已经完成,该方法会立刻返回操作结果,否则它会阻塞线程,直到操作完成,返回相应的结果。
代码示例:
/**
* 异步调用测试
* Future 接口在 Java 5 中被引入,设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。
* 在Future中触发那些潜在耗时的操作把调用线程解放出来,让它能继续执行其他有价值的工作,不需要等待耗时的操作完成。
*/
public class FutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// // 没有返回值的异步回调,void泛型,runAsync方法获得
// CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
// try {
// // 模拟延时观察效果
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + "runAsync => void");
// });
// System.out.println("1111");
// // 异步回调,获得执行结果。这里调用之后才能执行runAsync中的的线程的内容
// completableFuture.get();
// 有返回值的异步回调,supplyAsync方法获得,供给型函数式接口
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
// 模拟延时观察效果
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "runAsync => String");
// 模拟一个异常
// int a = 10 /0 ;
return "有返回值";
});
System.out.println("1111");
// 异步回调,获得执行结果。这里调用之后才能执行runAsync中的的线程的内容
// System.out.println(completableFuture.get());
// completableFuture的异步方法执行时,执行成功情况
// 这里的t是supplyAsync方法的返回值,u是一个继承Throwable接口的类型,是supplyAsync方法可能会产生的一个异常,
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t -> " + t);
System.out.println("u -> " + u);
})
// 执行失败情况
.exceptionally((e) -> {
System.out.println(e.getMessage());
return "执行出错";
})
// 异步回调,获得执行结果。这里调用之后才能执行runAsync中的的线程的内容
.get());
}
}