9、读写锁
ReadWriteLock:读可以被多线程同时读,写的时候只能有一个线程去写
测试代码:
package pers.mobian.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest01 {
public static void main(String[] args) {
MyCacheLock myCacheLock = new MyCacheLock();
//写入
for (int i = 1; i <= 5; i++) {
int temp = i;
new Thread(() -> {
myCacheLock.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
//读取
for (int i = 1; i <= 5; i++) {
int temp = i;
new Thread(() -> {
myCacheLock.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
//自定义一个我们的缓存
class MyCacheLock {
//自定义一个map集合
private volatile Map<String, Object> map = new HashMap<>();
//定义一个读写锁
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//写入
public void put(String key, Object value) {
//在写入数据的时候,使用我们的写锁
readWriteLock.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 {
readWriteLock.writeLock().unlock();
}
}
//读取
public void get(String key) {
//在读取数据的时候,使用我们的写锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始读取");
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
总结:我们本可以直接使用Lock锁进行锁的,但是为了更加小粒度,我们就使用读写锁。我们虽然在读和写的时候都添加了锁,但是只会在写入的时候显示出效果。
其他地方也会听到两个名词:独占锁(写锁)和共享锁(读锁)
10、阻塞队列
BlockingQueue
10.1、概述
写入时:如果队列满了,就会阻塞等待
读取时:如果队列是空的,就会阻塞等待生产
使用阻塞队列的场景:多线程并发处理、线程池!
补充:BlockingQueue(阻塞队列)、Deque(双端队列)、AbstractQueue(非阻塞队列)
相关接口以及类之间的继承关系:
10.2、学会使用API
学会使用队列的添加和移除操作
四组API:
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(,) |
移除 | remove() | poll() | take() | poll(,) |
获取队首元素 | element() | peek() | - | - |
第一种:抛出异常的方式
public static void test01() {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
queue.add("a");
queue.add("b");
queue.add("c");
//IllegalStateException: Queue full
//queue.add("d");
//打印队首元素
System.out.println(queue.element());
queue.remove();
queue.remove();
queue.remove();
//NoSuchElementException
//queue.remove();
}
第二种方式:有返回值,不抛出异常
public static void test02(){
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.offer("a"));
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
//当容量超过队列容量时,有返回值,不抛出异常
//System.out.println(queue.offer("d"));
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
//当我们的队首没有元素的时候,返回null
System.out.println(queue.peek());
//当队列没有元素的时候,返回null
System.out.println(queue.poll());
}
第三种方式:阻塞等待
public static void test03() throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
queue.put("a");
queue.put("b");
queue.put("c");
//会一直阻塞,进入等待期
//queue.put("d");
queue.take();
queue.take();
queue.take();
//也会一直阻塞,进入等待期
//queue.take();
}
第四种方式:超时等待
//第四种
public static void test04() throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.offer("a",2, TimeUnit.SECONDS));
System.out.println(queue.offer("b",2, TimeUnit.SECONDS));
System.out.println(queue.offer("c",2, TimeUnit.SECONDS));
//其他用法与第二种方式相同。等待两秒以后,再返回与第二种形式相同结果
System.out.println(queue.offer("d",2, TimeUnit.SECONDS));
System.out.println(queue.poll(2, TimeUnit.SECONDS));
System.out.println(queue.poll(2, TimeUnit.SECONDS));
System.out.println(queue.poll(2, TimeUnit.SECONDS));
//等待两秒以后,再返回与第二种形式相同结果
System.out.println(queue.poll(2, TimeUnit.SECONDS));
}
10.3、同步队列
SynchronousQueue(同步队列)与BlockingQueue(阻塞队列)不一样,同步队列不存储元素。
每当同步队列put一个值以后,必须先使用take取出来,否则不能再put进去值。
测试代码:
package pers.mobian.blockingqueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class Test02 {
public static void main(String[] args) {
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println("放入一个元素a");
synchronousQueue.put("a");
System.out.println("放入一个元素b");
synchronousQueue.put("b");
System.out.println("放入一个元素c");
synchronousQueue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("移除一个元素");
synchronousQueue.take();
TimeUnit.SECONDS.sleep(3);
System.out.println("移除一个元素");
synchronousQueue.take();
TimeUnit.SECONDS.sleep(3);
System.out.println("移除一个元素");
synchronousQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
总结:每次我们put一个值以后,就不能够再继续put值了,需要使用take,将数据取出,这样才能再次put值进去
11、线程池
线程池:3大方法、7大参数、4种拒绝策略
池化技术:事先准备好一些资源,有人要使用,就去拿,使用完毕之后再归还。
线程池的好处:
- 降低资源的消耗
- 提高相应的速度
- 方便管理
线程复用、可用控制最大并发数、管理线程
11.1、3大方法
package pers.mobian.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool3Method {
public static void main(String[] args) {
//1.创建一个只有一个线程的线程池
//ExecutorService threadPool = Executors.newSingleThreadExecutor();
//2.创建一个可伸缩的线程池
//ExecutorService threadPool = Executors.newCachedThreadPool();
//3.创建一个指定最大数量的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
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();
}
}
}
11.2、7大参数
我们查看3大方法的底层源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
本质:三大方法的本质都是去实例化了一个ThreadPoolExecutor类
ThreadPoolExecutor的构造方法的方法体:
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;
}
场景:正常情况下,就只会开启两个Core线程,然后新开启的线程就在阻塞队列中。当阻塞队列满了以后,就会开启Max线程,来保证更多的线程能够执行。如果线程还在不断的增加,最终达到了线程的最大值,就会使用拒绝策略,不再接受对应的线程请求。当线程业务处理完毕以后,指定的时间以后,就可以关闭因业务量后来新开的线程,即窗口3、4、5。
根据图示,自定义线程池:
package pers.mobian.threadpool;
import java.util.concurrent.*;
public class ThreadPool7Nums {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,//核心的窗口数量
5,//最大的窗口数量
3,//等待一定事件后关闭最大窗口
TimeUnit.SECONDS,//等待时间的单位
new LinkedBlockingDeque<>(3),//创建一个消息队列
Executors.defaultThreadFactory(),//线程工厂,不逊要动
new ThreadPoolExecutor.AbortPolicy()//使用四种拒绝策略里面的哪一种
);
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();
}
}
}
11.3、4种拒绝策略
RejectedExecutionHandler
在我们定义线程池的七个参数时,最后一个是使用何种拒绝策略
四种拒绝策略的具体实现类:
//多出来的线程,直接抛出异常
new ThreadPoolExecutor.AbortPolicy()
//谁开启的这个线程,就让这个线程返回给谁执行。比如main线程开启的,那就返回给main线程执行
new ThreadPoolExecutor.CallerRunsPolicy()
//如果队列线程数量满了以后,直接丢弃,不抛出异常
new ThreadPoolExecutor.DiscardPolicy()
//队列满了以后,尝试去和最早的线程竞争,也不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy
11.4、定义最大线程数
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,//最大的线程数量
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
定义最大线程数量的两种方法(用于调优)
1、CPU密集型
根据我们电脑的最大核数进行设置,以求达到平行的高效性。由于不同的电脑的核数不相同,所以我们不能写死,需要使用Java代码去动态的获取电脑核数的大小
Runtime.getRuntime().availableProcessors()
2、IO密集型
首先我们需要知晓我们程序中有几个十分消耗IO的线程。如果是n个,那么我们就需要大于n就行,一般使用2n个。
12、四大函数式接口
新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口
例如:Runnable接口底层和forEach方法底层
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
1234
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
final Object[] es = elementData;
final int size = this.size;
for (int i = 0; modCount == expectedModCount && i < size; i++)
action.accept(elementAt(es, i));
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
四大函数式接口:Function、Predicate、Consumer、Supplier
12.1、Function
函数型接口
Function接口体:传入一个输入参数,返回一个输出参数。并且只要是函数式接口,就可以使用lambda表达式进行简化书写
//参数:T是传入的参数,R是返回的参数
public interface Function<T, R> {
R apply(T t);
}
具体的代码实现:
package pers.mobian.function;
import java.util.function.Function;
public class Test01 {
public static void main(String[] args) {
// 常规的方法体
// Function function = new Function<String, String>() {
// @Override
// public String apply(String s) {
// return "==>"+s;
// }
// };
//使用lambda表达式简化开发
Function function = (s)->{return "==>"+s;};
System.out.println(((Function<String, String>) function).apply("2"));
}
}
12.2、Predicate
断定型接口
Predicate接口体:传入一个参数,根据对应的逻辑返回相应的boolean值
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
具体的代码实现:
package pers.mobian.function;
import java.util.function.Predicate;
public class PredicateTest01 {
public static void main(String[] args) {
// Predicate predicate = new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.equals("s");
// }
// };
//使用lambda表达式进行简化书写
Predicate predicate = (s)->{return s.equals("s");};
System.out.println(predicate.test("s"));
}
}
12.3、Consumer
消费型接口
Consumer接口体:只有输入没有返回值
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
具体的代码实现:
package pers.mobian.function;
import java.util.function.Consumer;
public class ConsumerTest01 {
public static void main(String[] args) {
// Consumer consumer = new Consumer<String>(){
// @Override
// public void accept(String str) {
// System.out.println("直接打印语句");
// }
// };
//使用lambda表达式简化书写
Consumer consumer = (a)->{
System.out.println("直接打印语句");
};
consumer.accept("s");
}
}
12.4、Supplier
供给型接口
Supplier接口体:不需要传入参数,直接
@FunctionalInterface
public interface Supplier<T> {
T get();
}
具体的代码实现:
package pers.mobian.function;
import java.util.function.Supplier;
public class SupplierTest01 {
public static void main(String[] args) {
// Supplier supplier = new Supplier<String>() {
// @Override
// public String get() {
// return "OK";
// }
// };
//使用lambda表达式简化开发
Supplier supplier = ()->{return "OK";};
System.out.println(supplier.get());
}
}
13、Stream流式计算
对于大量的数据(大数据 = 存储 + 计算),我们可以使用集合或者MySQL进行存储与计算。但是我们应该明确一个点,他们的本职工作应该是用来存储数据。
那么此时,我们的计算就应该交给流
我们就可以使用 java.util.stream
来进行计算。
结合lambda表达式、链式编程、函数式接口、Stream流式计算的小案例
题目要求:
- 使用Stream进行筛选计算
- 输出的id必须为偶数
- 年龄必须大于23岁
- 用户名转为大写字母
- 用户名字字母倒着排序
- 只能输出一个用户
测试代码:
//使用lombok插件
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private int id;
private String name;
private int age;
}
123456789
package pers.mobian.stream;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class StreamTest01 {
public static void main(String[] args) {
User user1 = new User(1, "a", 21);
User user2 = new User(2, "b", 22);
User user3 = new User(3, "c", 23);
User user4 = new User(4, "d", 24);
User user5 = new User(6, "e", 25);
//使用List集合,存储五个用户
List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
//System.out.println(list);
//使用Stream流进行计算
list.stream()
.filter((user) -> {return user.getId() % 2 == 0; })
.filter((user) -> {return user.getAge() > 23;})
.map((user -> {return user.getName().toUpperCase();}))
//将结果逆序排列
.sorted((u1, u2) -> {return u2.compareTo(u1);})
//分页显示一个数据
.limit(1)
.forEach(System.out::println);
}
}
14、ForkJoin
分支合并,分而治之
ForkJoin出现在JDK1.7中,为了并行的执行任务,提高效率而生的框架。只有在处理大量的数据的时候才能显现它的作用。
大数据下的Map Reduce的执行原理,是将大任务拆分为小任务,如图:
ForkJoin
特点:工作窃取
我们维护的都是一个双端队列。当B线程执行完毕以后,它会去帮助A线程执行,由于是双端队列的缘故,所以两边都能够开始执行。工作窃取,也就是偷取别人的工作,让总体的工作时间更短。
但是双端队列有一个问题,如果B线程执行完了以后去帮助A线程执行,容易出现抢占资源的问题。
使用方式:继承RecursiveTask(本质是ForkJoinTask),重写里面的抽象方法compute即可
public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
private static final long serialVersionUID = 5232453952276485270L;
V result;
//继承该类
protected abstract V compute();
public final V getRawResult() {...}
protected final void setRawResult(V value) {...}
protected final boolean exec() {...}
}
计算大量数字的一个小案例:比较普通for、ForkJoin、Stream三种不同的方式的计算速度
package pers.mobian.forkjoin;
import java.util.concurrent.RecursiveTask;
//编写一个计算方法
public class ForkJoinDemo extends RecursiveTask<Long> {
private long start;
private long end;
//临界值
private long temp = 1000000;
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 middle = (start + end) / 2;
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);
//把一个任务拆分为两个小任务,并且将两个任务压入队列中
task1.fork();
task2.fork();
//返回计算以后的结果
return task1.join() + task2.join();
}
}
}
package pers.mobian.forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
public class ForkJoinTest01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test01();
test02();
test03();
}
//使用最基本的循环遍历计算
public static void test01() {
long start = System.currentTimeMillis();
long sum = 0;
for (long i = 0; i <= 10_0000_0000L; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("花费的时间是:" + (end - start) + "结果是" + sum);
}
//使用ForkJoin进行计算
public static void test02() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
//创建一个ForkJoinPool池
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinDemo task = new ForkJoinDemo(0L, 10_0000_0000L);
//将自己的任务,提交至ForkJoinPool池中
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("花费的时间是:" + (end - start) + "结果是" + sum);
}
//使用Java8的新特性,进行流式计算
public static void test03() {
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("花费的时间是:" + (end - start) + " 结果是" + sum);
}
}
总结:理论上是普通的for最慢,然后是ForkJoin中间,Stream最块。但是在我实际的测试中,ForkJoin才是最慢的,网上也看了很多别人的分析,但是没有找到答案。有知道的你们,也可以给我说说,是什么改了???
15、异步回调
我们常听说的异步是Ajax,其实在Java里面也有异步回调的说法,甚至还有专门的处理方式。
Java通过Future接口来实现,对将来的某个事件的结果进行建模
具体的实现代码:
没有返回值的runAsync异步回调
package pers.mobian.future;
import java.util.Scanner;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class FutureTest01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值的runAsync异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()+"==>OK");
}
});
System.out.println("123456");
completableFuture.get();
}
}
有返回值(t, u)的supplyAsync异步回调
package pers.mobian.future;
import java.util.Scanner;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class FutureTest01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//有返回值(t, u)的supplyAsync异步回调
//返回值是错误的信息
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
//添加一个异常语句
int i = 10/0;
System.out.println(Thread.currentThread().getName() + "==>OK");
return 1024;
});
//获取异步回调对象的返回值(t, u),并打印。如果成功执行,就直接打印,如果执行出错,就执行对应的异常处理方法
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t=>" + t);
//u为错误的信息
System.out.println("u=>" + u);
}).exceptionally((e) -> {
//输出错误信息:java.lang.ArithmeticException: / by zero
System.out.println(e.getMessage());
return 333;
}).get());
}
}