JUC
juc->java.util.concurrent:并发编程中的工具类
- 三个包:concurrent,atomic,locks
进程和线程
- 进程:后台运行的一个程序,是操作系统分配资源的基本单位
- 线程:进程内部使用进程分配到的资源的,实现的某些功能,就是线程。
并发和并行
- 并发:而并发是指两个或多个事件在同一时间间隔发生。
- 并行:并行是指两个或者多个事件在同一时刻发生。
多线程编程企业级套路
- 1.在高内聚和低耦合的要求下,线程(匿名内部类) 操作(堆外暴露的方法) 资源类
- 2.判断/干活/通知
- 3.多线程交互,判断用while不用if
- 4.注意标志位的修改和定位
lock
class X{
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock();
try{
//method body
}finally{
lock.unlock()
}
}
}
- ReentrantLock:可重入锁
java多线程的多种状态
-
1.NEW:新建
-
2.RUNNABLE:就绪准备
-
3.BLOCKED:阻塞
-
4.WAITTING:一直等
-
5.TIMED_WATTING:等指定时间
-
6.TERMINATED:结束
-
wait和sleep的区别:
- wait:放开资源
- sleep:不放开资源
Lambda Express
- 口诀:拷贝小括号,写死右箭头,落地大括号。
- 函数注释:@FunctionInterface
- Java8之后可以在接口中有默认实现,因此需要加权限保护为default。
- 静态方法实现:可以在接口中实现静态方法
线程间通信
Thread.wait()->Thread.notifyAll()
- 多线程交互中,必须防止多线程的虚假唤醒,即不需使用if只能使用while,因为用if的时候,可能存在判断if之后,执行业务之前产生一次wait,这样导致再次调度到这个线程的时候,可能执行了另一个操作,影响到本身的数据。
- 用JUC的包,wait->notifyAll变成了:
Condition condition = lock.newCondition();
condition.await();——>condition.signalAll();
- synchronized的wait/notify 和Condition的await/signal 的区别,可以通过创造多个condition进行精准打击
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
8锁现象
有两个方法 email和EMS,synchronized修饰
class Test{
public synchronized void email(){
System.out.println("email");
this.thread.sleep(4000);
}
public synchronized void ems(){
System.out.println("ems");
}
}
public void static main(args[]){
Test test1 = new Test();
Test test2 = new Test();
new Thread(()->{
test1.email();
test1.ems();
}).start();
}
- 1.标准访问:顺序执行
- 2.email方法sleep4秒,先打印email,后打印ems
- 3.新增一个普通方法hello,先打印hello,后打印email和ems
- 4.如果有两个资源类,第二个线程调用第二个资源类的EMS,先打印EMS
- 5.两个静态同步方法,同一个资源类,先打印Email,后打印EMS。
- 6.两个静态同步方法,两个资源类,先打印Email,在打印EMS
- 7.一个普通同步方法,一个静态同步方法,先打印EMS,再打印邮件
- 8.一个普通同步方法,一个静态同步方法,两个资源,先打印EMS,再打印email
8锁解释
- 1.第一个二现象因为普通synchronized修饰的方法,锁的当前的实例对象,因此多个线程不能同时访问同一个实例对象中的synchronized修饰的方法,因此按照线程的启动顺序执行方法。所以1,2两个执行方法顺序是这样的。
- 2.第三个现象之所以普通方法会先执行,不受锁的影响,因为没有经过synchronized修饰的方法,不在受到锁影响的资源中,可以执行。
- 3.两个资源类的情况,因为每个线程锁的各自实例对象的资源,并不会影响到其他线程运行自己实例对象的资源。
- 4.第五个和第六个静态同步方法:因为锁的是Class这个东西,因此,只要是用.Class实例化的对象的静态锁,本质上是一把锁。
- 5.第七个和第八个,因为一个是锁的静态方法即锁的是这个类,一个锁的是已经实例化的对象,本质上并不是同一个把锁,所以不持有另一个现成的资源,因此,先执行EMS后执行email(email在sl)
List线程不安全
- 高并发导致线程不安全的异常:java.util.ConcurrentModificationException
- 不安全的原因:
- 进行资源操作的方法没有上锁
- 不安全怎么办
- 1.用vector这种线程安全的集合
- 2.用Collections.synchronizedList(new ArrayList<>())
- 3.用JUC中的 java.util.Concurrent.CopyOnWriteArrayList.class
- vector为什么线程安全:因为add方法经过synchronized的修饰
- Collections.synchronizedList:他的add方法是这样的:
synchronized (mutex) {list.add(index, element);}}
- CopyOnWriteArrayList:写时复制
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线程不安全
- 高并发导致的问题跟list一样,ConcurrentModificationException
- 解决办法:
- CopyOnWriteArraySet类
- HashSet底层是HashMap 放到HashMap的Key的位置,value是一个
private static final Object PRESENT = new Object();
- HashSet的add()底层源码就是实现HashMap的put()
Map线程问题
- HashMap底层是一个队列+链表+红黑树的数据结构,他的负载因子是0.75,初始值大小是16。
- HashMap的线程并不安全,因此可以使用HashTable和ConcurrentHashMap
- HashTable在修改数据是锁住了整个HashTable,效率较低
- ConcurrentHashMap把整个Map分为N个Segment,提供相同的线程安全,运用了锁分离技术。
- HashMap调优可以选择初始值设置较大,减少扩容次数使用的时间。
- HashMap中的链表长度大于8,就会把单向链表变化成红黑树,jdk1.7之前采用头插法,jdk1.8之后采用尾插法,因为采用头插法在扩容的时候可能会产生环形链表。
多线程获取方法
- 常见的方法
- 1.Thread
- 2.实现Runnable接口
- 3.实现Callable结构
- 4.线程池(重点)
- Callable和Runnable接口的区别
- 1.方法不同,一个是run,一个是call
- 2.call会抛出异常
- 3.call有返回值
- Theard的构造器必须实现了Runnable,因此我们需要找一个实现了Runnable和与Callable有关的类,即FutureTask类。
- 如果通过两个线程调用同一个FutureTask,返回值会存在缓存中,因此只会执行一次call操作。
JUC的辅助工具类
- CountDownLatch类:倒计时,await取消的要求就是Count=0,底层有个Sync的类做封装。
- CyclicBarrier类:等指定的数量暂停,执行构造器里面的Run代码
- Semaphore类:控制多线程的并发数量
ReenTrantReadWriteLock读写锁
- 多个线程访问一个资源类,不会产生并发的问题,因此为了满足并发量,读取共享资源应该可以同时进行。
- 但是写操作会产生资源的变化,因此对写操作要进行锁。
- 跟Lock的区别,Lock的锁是读写都锁,效率低,readWriteLock读写分离。
- 获取读锁和写锁的方法是:
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.writeLock().lock();
readWriteLock.readLock().lock();
BlockingQueue
- 当队列是空的,从队列中获取元素的操作将会被阻塞。
- 当队列是满的,向队列中添加元素的操作将会被阻塞。
- BlockingQueue的好处是我们不需要关心什么时候需要阻塞线程,这一切交割BlockingQueue解决。
- 常用实现类
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
- DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
- SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
- LinkedTransferQueue:由链表组成的无界阻塞队列。
- LinkedBlockingDeque:由链表组成的双向阻塞队列。
ThreadPool线程池
- 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
- 线程池的优势:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
- 提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
- 重要的接口:
- Executor接口
- ExecutorService接口
- 实现线程池的类是:ThreadPoolExcutor
- 创建线程池的工具类:Excutors,可以创建如下的线程池:
- newFixedThreadPool(5)
- newSingelThreadPool()
- newCacheThreadPool():执行很多短期异步任务,线程池根据需要创建新线程,先前构建的线程可以复用。
- ThreadPoolExcutor源代码:上面的工具类返回值都是ThreadPoolExcutor类。
- 线程池几个重要参数:
- corePoolSize:线程池中常驻核心线程数量
- maxmumPooSize:线程池中能够容纳同时执行的最大线程数,必须大于等于1。
- keepAliveTime:多余空闲线程的存活时间,当前线程超过corePoolSize是,当空闲时间达到keepAliveTime是,多于线程池会被销毁
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交但是尚未被执行的任务
- threadFactory:生成线程池中工作线程的线程工厂,用于创建线程,一般默认即可。(可以通过实现ThreadFactory接口自己实现)
- handler:拒绝策略,当队列满了,并且工作线程大于等于线程池的最大线程数时,如何来拒绝请求执行的runnable的策略。
- 线程池底层工作原理:
- 初始开启corePoolSize
- 当请求线程大于corePolsize,就将后来的请求放入Blocking Queue
- 当BlockQueue满了,将当前常驻线程数进行扩容
- 如果当所有的都满了,就开始执行reject策略
- 工作中只允许用自己创建的线程池,不允许用jdk自带,参考阿里巴巴java开发手册,因为JKD自带的max数量是21亿多,会导致OOM。
- 自定义线程池:手撕线程池!!!
package juc;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
60L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
new MyThreadFactory("jams"),
new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
- 自定义线程工厂
package juc;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class MyThreadFactory implements ThreadFactory {
private final String prefix;
private final AtomicInteger nextId = new AtomicInteger(1);
//通过构造器决定这个工厂生产哪个组的线程
MyThreadFactory(String groupFeatureName) {
prefix = "MyThreadFactory " + groupFeatureName + "-Worker-";
}
@Override
public Thread newThread(Runnable r) {
String name = prefix + nextId.getAndIncrement();
Thread thread = new Thread(null, r, name, 0);
return thread;
}
}
- 四种拒绝策略:
- AbortPolicy():拒绝接受的情况下,直接弹出RejectedExecutionException异常
- CallerRunsPolicy():该策略既不会抛弃任务,也不会抛出异常,而是将某些任务返回给调用者执行。
- DiscardPolicy():不会报错,但是超过数量的请求,直接抛弃不执行。
- DiscardOldestPolicy():按照先来后到,丢弃最后的任务
- 参数选择
- maximumPoolSize:
1.cpu密集型:核数+1/2
2.IO密集型:核数/阻塞系数
- maximumPoolSize:
JAVA8流式处理
- 主要接口:
- java.util.Stream
- java.util.Function四大函数式接口
- Consumer:消费性接口 ->有输入T,没输出
- Supplier:供给型接口 ->没输入,有输出T
- Function<T,R>:函数型接口 ->有输入T,有输出R
- Predicate:断定型接口 -> 有输入T,有输出true/false
- 语法和api跟scala的rdd一样
ForkJoinPool
- ForkJoinPool是线程池ExecurotService的实现类。
- 通过继承RecursiveTask抽象类,实现compute()方法
- 作用和hadoop的MapReduce有点像,分为fork和join两步,对应map和reduce(combine)
代码
package juc;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
class MyFork extends RecursiveTask<Integer> {
private int start;
private int end;
private int result;
public MyFork(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start < 10) {
for (int i = start; i <= end; i++) {
result += i;
}
} else {
int mid = (start + end) / 2;
MyFork fork1 = new MyFork(start, mid);
MyFork fork2 = new MyFork(mid + 1, end);
//先fork下去,再join
fork1.fork();
fork2.fork();
result = fork1.join() + fork2.join();
}
return result;
}
}
public class ForkJoinDemo02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyFork fork = new MyFork(0, 100);
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Integer> submit = forkJoinPool.submit(fork);
System.out.println(submit.get());
forkJoinPool.shutdown();
}
}
异步回调
- CompletableFuture类
- 方法:runAsync()有返回值和没有返回值两种,异步执行
- 方法:supplyAsync()没有输入,有返回值,异步执行