本文已参与「新人创作礼」活动,一起开启掘金创作之路。
概念
-
进程 && 线程
-
线程状态
-
wait && sleep
-
并发 && 并行
-
java.util.concurrent在并发编程中使用的工具类
- java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.locks
-
多线程模板编程
- 高内聚低耦合
- 线程——操作——资源类
-
synchronized与Lock的区别
- 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
- synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
- synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
- 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
- synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
- 只有一个方法的接口,称为函数式接口(默认自带该@FunctionalInterface注解)
- Lambda表达式:拷贝小括号,写死右箭头,落地大括号
- 多线程交互中,必须要防止多线程的虚假唤醒(生产者满足某个条件时,理应通知某一个线程去消费,却唤醒所有的线程消费而产生非正常结果)。解决:判断只能用while,不能用if
- Lock类替换synchronize关键字,Condition的signaAll方法替换Object的notifyAll方法
- Condition的精确通知顺序访问:利用一个标记位,signal方法实现具体通知某个线程
- ArrayList扩容默认为当前的一半,HashMap扩容为当前的一倍,默认容量是16(2^4^),扩容即为32(2^5^),默认加载因子为0.75
多线程8锁
class Phone{
public synchronized void print1(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
public synchronized void print2(){
System.out.println("2");
}
public void hello(){
System.out.println("hello");
}
}
/**
* @description: 多线程8锁
* 1.标准访问,先输出1还是输出2? answer:按顺序访问,1 2
* 2.1方法暂停1秒,先输出1还是输出2? answer:方法同步,1方法暂停,2方法等待,1 2
* 3.新增普通hello方法,先输出1还是输出hello? answer:hello方法不是同步方法,无需等待,hello 1
* 4.两部手机,先输出1还是输出2? answer:对象不同,无需等待方法1,2 1
* 5.两个静态同步方法,同一部手机,先输出1还是输出2? answer:静态方法锁当前资源类.class文件,需等待,1 2
* 6.两个静态同步方法,两部手机,先输出1还是输出2? answer:两个静态同步方法,锁当前资源类,1 2
* 7.一个普通同步方法,一个静态同步方法,一部手机,先输出1还是输出2? answer:静态同步方法,锁当前资源类,普通同步方法锁当前对象,锁的不同,2 1
* 8.一个普通同步方法,一个静态同步方法,两部手机,先输出1还是输出2? answer:静态同步方法,锁当前资源类,普通同步方法锁当前对象,锁的不同,2 1
*
* @author XLJ
* @date 2020/10/19 19:24
*/
public class Lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.print1();
},"A").start();
// Thread.sleep(1000);
new Thread(()->{
// phone2.print2();
phone1.print2();
},"B").start();
}
}
Callable
获得多线程方式:传统的是继承thread类和实现runnable接口, java5以后又有实现callable接口和java的线程池获得
Callable与Runnable接口区别:
- Runnable重写run方法,Callable重写call方法
- 泛型,Callable有返回参数类型
- 重写call方法抛出异常
Callable的实现:
- Thread类中没有Callable的构造方法,需要通过“中介”去实现
- FutureTask类实现了Runnable接口,同时有Callable的构造方法
- 通过FutureTask类实现
- FutureTask.get方法获得Callable的返回参数
- 同一个FutureTask对象在被多个线程调用时,只会被调用一次
集合类不安全
- 线程不安全错误:java.util.ConcurrentModificationException(异常并发修改异常)
List不安全
- 解决方案:
- new Vector
- Collections.synchronizedList(new ArrayList())
- 写时复制:CopyOnWriteArrayList
Set不安全
- Collections.synchronizedSet()
- CopyOnWriteArraySet
- HashSet底层数据结构是HashMap中的Key
Map不安全
- Collections.synchronizedMap()
- ConcurrentHashMap
辅助类
CountDownLatch减少计数
- CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
- 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
- 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。
CyclicBarrier循环栅栏
- 字面意思是可循环(Cyclic)使用的屏障(Barrier)。
- 它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
- 线程进入屏障通过CyclicBarrier的await()方法。
Semaphore信号灯
- 在信号量上我们定义两种操作:
- acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。
- release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
- 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
阻塞队列BlockQueue
- 栈:先进后出;队列:先进先出
- 当队列是空的,从队列中获取元素的操作将会被阻塞
- 当队列是满的,从队列中添加元素的操作将会被阻塞
- 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
- 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增
核心种类
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。
- SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
核心方法
线程池ThreadPool
特点:线程复用、控制最大并发数、管理线程。
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
- 提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
7大参数
- corePoolSize:线程池中的常驻核心线程数
- maximumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于1
- keepAliveTime:多余的空闲线程的存活时间当前池中线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程为止
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交但尚未被执行的任务
- threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认的即可
- handler:拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略
(重要)底层工作原理
-
在创建了线程池后,开始等待请求。
-
当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
- 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
- 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
- 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
- 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
-
当一个线程完成任务时,它会从队列中取下一个任务来执行。
-
当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
- 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
- 所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
拒绝策略
- AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
- CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加人队列中尝试再次提交当前任务。
- DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。
注意
Stream流式计算
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
四大函数式接口
代码
list.stream()
.filter(p -> {return p.getId() % 2 == 0;})
.filter(p -> {return p.getAge() > 24;})
.map(f -> {return f.getUserName().toUpperCase();})
.sorted((o1, o2) -> {return o2.compareTo(o1);})
.limit(1)
.forEach(System.out::println);
分支合并框架ForkJoin
/**
* ForkJoin分支合并框架
* ForkJoinPool、ForkJoinTask、Recursive(递归)Task
* 核心思想:通过递归将一个线程不断fork拆分多个子线程,最后join合并,继承RecursiveTask抽象类,实现computer方法
*
* @author xlj
* @date 2020/10/27 22:44
*/
class MyTask extends RecursiveTask<Integer> {
private static final Integer LIMIT_VALUE = 10;
// 计算初始值
private int start;
// 计算结束值
private int end;
// 结果
private int result;
public MyTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
// 当任务较小,无需拆分
if ((end - start) <= LIMIT_VALUE) {
for (int i = start; i <= end; i++) {
result += i;
}
} else {
int middle = (end + start) / 2;
// 递归fork拆分
MyTask myTask1 = new MyTask(start, middle);
MyTask myTask2 = new MyTask(middle + 1, end);
myTask1.fork();
myTask2.fork();
result = myTask1.join() + myTask2.join();
}
return result;
}
}
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 资源类
MyTask myTask = new MyTask(50, 100);
// 线程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 线程-操作-资源类
ForkJoinTask<Integer> task = forkJoinPool.submit(myTask);
// 通过get方法获取返回值
System.out.println("hello,ForkJoin");
System.out.println(task.get());
forkJoinPool.shutdown();
}
}
异步回调
public class CompleteFutureDemo {
public static void main(String[] args) throws Exception {
// 异步无返回值
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + "执行完毕,无返回值");
});
runAsync.get();
// 异步回调,有返回值-----supplyAsync(供给型接口---无传入参数,有返回值)
CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "执行完毕,有返回值");
// int a = 10/0;
return 1024;
});
supplyAsync.whenComplete((t, u) -> {
System.out.println("********" + t);
System.out.println("********" + u);
}).exceptionally(t -> {
System.out.println("*********" + t.getMessage());
return 404;
}).get();
}
}