JUC
1.JUC概述
1.1 什么是JUC
java.util.concurrent包,java并发工具包。
2.进程和线程
2.1 进程和线程概念
- 进程:指在系统中正在运行的一个应用程序,程序一旦运行就是一个进程。进程-系统资源分配的最小单位。
- 线程:系统分配处理器时间的的基本单元,线程-程序执行的最小单位。
2.2 线程的状态
- NEW(新建状态)
- RUNNABLE(就绪状态)
- BLOCKED(阻塞状态)
- WAITING(等待状态)
- TIMED_WAITING(超时不候)
- TERMINATED(终结状态)
2.3 wait和sleep的区别
- sleep是Thread的方法,wait是Object的方法,任何对象实例都可以调用。
- sleep不会释放锁,wait会释放锁,前提是当前线程要占用锁
- 他们都可以被interrupted方法中断。
2.4 并发和并行
- 并发:同一时刻,指的是多个事情,在同一时间段内同时发生了,多个任务之间是互相抢占资源的。
- 并行:指的是多个事情,在同一时间点上同时发生了,多个任务之间是不互相抢占资源的。
- 只有在多CPU或者一个CPU多核的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的。
2.5 管程
即Monitor监视器,所说的锁。是一种同步机制,保证在同一时间内只有一个线程访问被保护资源或者代码。
2.6 用户线程和守护线程
- 用户线程:自定义线程
- 守护线程:处于后台运行的线程,比如垃圾回收等。
3.synchronized与Lock
3.1 synchronized和Lock的区别
- synchronized是java中的关键字,lock是java.util中的接口。
- synchronized发生异常时不需要手动去释放锁,lock需要手动去释放锁。
- lock等待锁的过程中可以使用interrupted来中断等待,而synchonized只能等待,不能响应中断。
- lock可以知道有没有获取锁,而synchonized不能。
- 在性能上来说,如果资源竞争不激烈,两者的性能差不多。而当资源竞争非常激烈时,lock的性能优于synchonized。
4.线程间通信
4.1 线程间通信
- synchronized线程间通信使用wait()、notify()、notifyAll()方法。
- lock线程间通信使用await()、signal()、signalAll()方法。
4.2 定制线程间通信
给各个线程增加标识位,使用lock.newCondition()方法创建不同的锁。
5.集合线程安全
5.1 arrayList线程安全
arrayList线程安全问题可以使用vector、Collections.synchronizedList、copyOnWriteArrayList解决。
涉及的底层原理为写时复制技术
- 读的时候并发(多个线程操作)
- 写的时候独立,先复制相同的空间到某个区域,将其写到新区域,旧新合并,并且读新区域(每次加新内容都写到新区域,覆盖合并之前旧区域,读取新区域添加的内容)
5.2 hashSet线程安全
hashSet线程安全问题可以使用CopyOnWriteArraySet()解决。
5.3 hashMap线程安全问题
hashMap线程安全问题可以使用ConcurrentHashMap()方法解决。
6.多线程锁
6.1 synchronized锁的对象和范围
- synchronized锁的是方法,则是对象锁
- 加了static则为class锁而不是对象锁
- 同个对象锁的机制要等待,不同对象锁的机制调用同一个不用等待
- 对于代码块,锁的是代码块里的对象
6.2 公平锁和非公平锁
- 公平锁:效率相对低,负载均衡
- 非公平锁:效率高,但是线程容易饿死
6.3 可重入锁
synchronized和lock都是可重入锁
- sychronized是隐式锁,不用手工上锁与解锁,而lock为显示锁,需要手工上锁与解锁
- 可重入锁也叫递归锁
6.4 死锁
两个或以上的进程因为争夺资源而造成互相等待资源的现象称为死锁
6.4.1 产生死锁的原因:
- 系统资源不足
- 系统资源分配不当
- 进程运行顺序不当
6.4.2 验证是否是死锁
- jps 类似于linux中的ps -ef查看进程号
- jstack 自带的堆栈跟踪工具
7.callable接口
7.1 runnable和callable比较
- runnable重写run()方法,callbale重写call()方法。
- runnable无返回值,callable有返回值,通过FutureTask.get()。
- runnable的run方法不会引发异常,而callable的call()方法会引发异常。
7.1 FutureTask
FutureTask:未来任务。是runnable接口的实现类。在不影响主线程的情况下,单独开一个线程去执行其他任务,一次汇总。
8.JUC辅助类
- 减少计数CountDownLatch
- 循环栅栏CyclicBarrier
- 信号灯Semaphore
9.读写锁
9.1 乐观锁和悲观锁
- 悲观锁:认为每次都会修改,每个线程都会上锁,不支持并发。
- 乐观锁:比较乐观,支持并发。通过版本号保证。
9.2 表锁和行锁
- 表锁:对整张表加锁,不会发生死锁。
- 行锁:对一行加锁,会发生死锁。
9.3 读写锁
- 读锁:共享锁,支持并发,可能发生死锁,读的时候也可修改操作。
- 写锁:独占锁,不支持并发,可能发生死锁。synchronized和ReentrantLock都是独占锁。
9.4 读写锁优点和缺点
- 优点
读可以共享,写只能独占 - 缺点
(1)造成锁饥饿,一直在读,没有写操作。
(2)读的时候不能写,只有读完成后才可以写,写的时候可以读。
9.5 读写锁的降级
获取写锁 -> 获取读锁 -> 释放写锁 -> 释放读锁
10.阻塞队列BlockingQueue
阻塞队列:是共享队列,一端输入,一端输出。
10.1 种类
- ArrayBlockingQueue:基于数组的阻塞队列,由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:基于链表的阻塞队列,由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。
- DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
- PriorityBlockingQueue:基于优先级的阻塞队列,支持优先级排序的无界阻塞队列。
- SynchronousQueue:一种无缓冲的等待队列。
- LinkedTransferQueue:由链表结构组成的无界阻塞 TransferQueue 队列
由链表组成的无界阻塞队列。 - LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
10.2 方法
11.ThreadPool线程池
回顾以前的连接池概念
连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
特点:
- 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
- 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
11.1 几种常见的创建类型方式
- Executors.newFixedThreadPool(int)一池N线程
- Executors.newSingleThreadExecutor()一池一线程
- Executors.newCachedThreadPool()一池可扩容根据需求创建线程
注:执行线程execute()、关闭线程shutdown()
11.2 工作流程
- 在执行创建对象的时候不会创建线程
- 创建线程的时候execute()才会创建
- 先到核心线程,满了之后再到阻塞队列进行等待,阻塞队列满了之后,在往外扩容线程,扩容线程不能大于最大线程数。大于最大线程数和阻塞队列之和后,会执行拒绝策略。
11.3 七个参数
- int corePoolSize, 核心线程数量(核心)
- int maximumPoolSize,最大线程数量
- long keepAliveTime,线程存活时间
- TimeUnit unit,线程存活时间单位
- BlockingQueue workQueue,阻塞队列(排队的线程放入)
- ThreadFactory threadFactory,线程工厂,用于创建线程
- RejectedExecutionHandler handler拒绝测试(线程满了)
11.4 拒绝策略
11.5 自定义线程池
阿里巴巴手册中建议实际在开发中不允许使用Executors创建,而是通过ThreadPoolExecutor的方式,规避资源耗尽风险
12.Fork/Join分支合并框架
将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果。该算法相当于递归,且是二分查找思路
ForkJoinTask:我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务。该类提供了在任务中执行 fork 和 join 的机制。通常情况下我们不需要直接集成 ForkJoinTask 类,只需要继承它的子类,Fork/Join 框架提供了两个子类:
RecursiveAction:用于没有返回结果的任务
RecursiveTask:用于有返回结果的任务
ForkJoinPool:ForkJoinTask 需要通过 ForkJoinPool.sumit() 来执行,使用后shutdown()关闭。
RecursiveTask: 继承后可以实现递归(自己调自己)调用的任务
13.CompletableFuture异步回调
CompletableFuture 在 Java 里面被用于异步编程,异步通常意味着非阻塞,可以使得我们的任务单独运行在与主线程分离的其他线程中,并且通过回调可以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息。
CompletableFuture 实现了 Future, CompletionStage 接口,实现了 Future接口就可以兼容现在有线程池框架,而 CompletionStage 接口才是异步编程的接口抽象,里面定义多种异步方法,通过这两者集合,从而打造出了强大的CompletableFuture 类。
使用:
- 异步调用没有返回值方法runAsync
- 异步调用有返回值方法supplyAsync,whenComplete(()->{})判断完成后执行。
- 主线程调用 get 方法会阻塞