大鹏一日同风起,扶摇直上九万里。这篇文章主要讲述Java知识体系Java并发编程进阶,多线程和锁底层原理探究相关的知识,希望能为你提供帮助。
||To Up||未来村村长正推出一系列【To Up】文章,该系列文章重要是对java开发知识体系的梳理,关注底层原理和知识重点。”天下苦八股文久矣?吾甚哀,若学而作苦,此门无缘,望去之。“该系列与八股文不同,重点在于对知识体系的构建和原理的探究。
文章目录
- ??||To Up||??
- ??一、Java多线程??????
- ??1、进程与线程??
- ??(1)进程??
- ??(2)线程??
- ??(3)线程与进程的区别??
- ??2、线程的创建??
- ??(1)继承Thread类??
- ??(2)实现Runnable接口??
- ??(3)使用Callable和FutureTask??
- ??(4)使用线程池创建??
- ??3、线程的状态与相关操作??
- ??(1)相关状态与操作??
- ??(2)守护线程??
- ??(3)线程组??
- ??① 创建??
- ??② 使用??
- ??③ 线程组的枚举??
- ??④ main线程组的获取??
- ??4、线程间通信??
- ??5、线程池的架构设计??
- ??(1)线程池架构设计??
- ??(2)Executors的使用与弊端??
- ??5、线程池标准创建方式与相关原理??
- ??(1)ThreadPoolExecutor??
- ??① 核心和最大线程数量??
- ??② BlockingQueue??
- ??③ keepAliveTime??
- ??(2)向线程池提交任务的两种方式??
- ??(3)任务调度流程??
- ??(4)线程工厂ThreadFactory??
- ??(5)任务阻塞队列??
- ??(6)线程池的拒绝策略??
- ??(7)线程池的状态与关闭??
- ??6、ThreadLocal:线程安全解决??
- ??(1)ThreadLocal原理??
- ??(2)与Synchnized区别??
- ??(3)ThreadLocal成员方法??
- ??二、Java内置锁??????
- ??1、synchronized关键字??
- ??(1)synchronized同步方法??
- ??(2)synchronized同步块??
- ??(3)Synchronized的两个推论??
- ??2、Java内置锁??
- ??(1)Java对象与内置锁??
- ??(2)内置锁的状态??
- ??① 无锁状态??
- ??② 偏向锁状态??
- ??③ 轻量级锁状态??
- ??④ 重量级锁状态??
- ??3、synchronized执行原理??
- ??4、CAS??
- ??(1)原理??
- ??(2)使用??
- ??(3)CAS操作的弊端和规避??
- ??三、JUC显式锁??????
- ??1、显式锁Lock接口??
- ??2、可重入锁ReentrantLock??
- ??3、显式锁的使用??
- ??(1)使用lock阻塞抢锁??
- ??(2)使用tryLock非阻塞抢锁??
- ??(4)使用tryLock限时抢锁??
- ??4、LockSupport??
- ??5、锁的类型??
- ??(1)可重入锁和不可重入锁??
- ??(2)悲观锁和乐观锁??
- ??(3)公平锁和非公平锁??
- ??(4)可中断锁和不可中断锁??
- ??(5)共享锁和独占锁??
- ??(6)读写锁??
- ??四、AQS:显式锁的原理??????
- ??1、AQS的组成??
- ??(1)状态标志位??
- ??(2)队列节点类??
- ??(3)FIFO双向同步队列??
- ??(4)钩子方法??
- ??2、AQS实现锁原理??
- ??(1)自己实现一个简单锁??
- ??(2)AQS锁抢占原理??
- ??① 锁抢占执行过程??
- ??② acquire??
- ??③ addWaiter()??
- ??④ acquireQueued()??
- ??(3)AQS释放锁原理??
- ??① 锁释放执行过程??
- ??② release()??
- ??② tryRelease()??
- ??③ unparkSuccessor(h)??
- ??3、AQS条件队列??
- ??(1)Condition基本原理??
- ??(2)await等待方法原理??
- ??(3)signal唤醒方法原理??
- ??五、JUC原子类???????
- ??(1)基本类型原子操作??
- ??(2)引用类型原子操作??
- ??六、volatile??????
- ??1、并发三大问题??
- ??(1)原子性问题??
- ??(2)可见性问题??
- ??(3)有序性问题??
- ??2、volatile原理??
(1)进程【Java知识体系Java并发编程进阶,多线程和锁底层原理探究】 一个进程是一个程序的一次启动和执行,一个进程一般由程序段、数据段、进程控制块组成:
- 程序段:代码段,需要执行的指令集合
- 数据段:需要操作的数据和程序运行时产生的数据
- 进程控制块:进程的描述信息和控制信息,是进程存在的唯一标志
- 进程的描述信息:进程ID和进程名称
- 进程的调度信息:程序的起始地址和通信信息
- 进程的资源信息:内存信息和文件句柄
- 进程上下文:CPU寄存器的值、当前程序计数器的值
(2)线程 线程是指“进程代码段”的一次顺序执行流程,线程是CPU调度的最小单位,而进程是操作系统资源分配的最小单位,线程之间共享进程的内存空间、系统资源。
线程的组成如下:
- 线程ID:线程的唯一标识,同一个进程内的线程ID不会重复
- 线程名称:用于用户识别,若没有显式命名系统会自动分配
- 线程优先级:表示线程调度的优先级,优先级越高获得CPU的执行机会越大
- 线程状态:分别为新建(NEW)、可执行(RUNNABLE)、阻塞(BLOCKED)、无限期等待(WAITING)、限时等待(TIMED_WAITING)、结束(TERMINATED)。
- 线程类型:是否为守护线程
2、线程的创建
(1)继承Thread类 Thread类是Java多线程编程的基础,通过继承Thread类创建线程类可以实现线程的创建:
- 继承Thread类,创建新的线程类
- 重写run()方法,将需要并发执行的业务代码编写在run()方法中
- 调用Thread实例的start()方法启动线程
- 线程启动后,线程的run方法将被JVM执行
(2)实现Runnable接口 Thread也实现了Runnable接口,且Thread中有以下构造方法可通过传入Runnable接口实现对象参数来实现线程的创建。
//系统定义名称
public Thread(Runnable target)
init(null, target, "Thread-" + nextThreadNum(), 0);
//自定义名称创建
public Thread(Runnable target, String name)
init(null, target, name, 0);
则通过实现Runnable接口来创建线程的步骤如下:
- 定义一个类实现Runnable接口
- 实现Runnable接口中的run()抽象方法【业务处理逻辑】
- 通过Thread的构造方法创建线程对象,传入Ruunable实例作为参数
- 调用Thread实例的start()方法启动线程
- 线程启动后,线程的run方法将被JVM执行,Thread的run方法将会调用Runnable实例的run方法
public void run()
if (target != null)
target.run();
我们也可以看看Runnable的源码,可以看到Runnable是一个函数式接口,即只有一个方法的接口,其与Thread都来自java.lang包。
package java.lang;
@FunctionalInterface
public interface Runnable
public abstract void run();
(3)使用Callable和FutureTask 在使用Callable和FutureTask之前我们先来看看它们的源码,来认识一下他们。首先是Callable,同样是一个函数式接口,其中的call与run类似,但是其具有返回值,可通过泛型来定义。
package java.util.concurrent;
@FunctionalInterface
public interface Callable< V>
V call() throws Exception;
但是我们知道要创建线程离不开Thread类,所以这里使用了FutureTask进行牵线搭桥。我们可以看到FutureTask类的声明和构造器。
public class FutureTask< V> implements RunnableFuture< V>
public FutureTask(Callable< V> callable)
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
FutureTask继承了RunnableFuture,其源码如下。所以我们可以想到,通过FutureTask构造器可构造一个Runnable实例,这样就可以传入Thread代理执行。
package java.util.concurrent;
public interface RunnableFuture< V> extends Runnable, Future< V>
void run();
使用Callable和FutureTask创建线程的步骤如下:
- 创建一个Callable接口的实现类,实现call()方法【编写业务逻辑并设置返回值】
- 使用Callable实现类的实例构造一个FutureTask实例
- 使用FutureTask实例作为Thread构造器的参数target创建一个线程
- 调用Thread实例的start()方法启动新线程,Thread的run方法会执行FutureTask的run方法,最终会调用Callable的call方法
线程池具体原理与操作后续会进行说明。
3、线程的状态与相关操作
(1)相关状态与操作 在Thread源码中使用enum枚举了Thread的六种状态:
- NEW(新建):线程创建,未调用start方法启动,这里会调用相应的init方法进行创建【new】
- RUNNABLE(可运行):线程处于可执行状态,已经在Java虚拟机执行,但还在等其它操作系统资源【start()】
- 就绪状态:调用start()、CPU时间片用完、sleep()操作结束、join()操作结束、抢到对象锁、调用yield()方法
- BLOCKED(阻塞):线程处于阻塞,线程在等待一个监控锁,特别是多线程下的场景等待另一个线程同步块的释放。【synchronized】
- WAITING(等待):线程处于等待状态,指的是该线程正在等待另一个线程执行某些特定的操作【wait()、join()】
- TIMED_WAITING(调校时间的等待):与时间相关的等待,调用了设定等待时长参数的方法【sleep(xx)、wait(xx)、LockSupport.parkNanos(xx)/parkUntil(xx)】
- TERMINATED(终止):线程执行完毕的状态或执行过程发生了异常
(2)守护线程 JVM进程中的GC线程就是一个守护线程,守护线程的使用有以下要点:
- 守护线程必须在启动前通过setDaemon()方法将状态设置为true,启动后就不能进行设置,否则报InterruptedException异常
- 守护线程存在被JVM强制终止的风险,所以在守护线程中尽量不去访问系统资源
- 守护线程中创建的线程也是守护线程
① 创建
ThreadGroup threadGroup01 = new ThreadGroup()
② 使用
Thread thread01 = new Thread(threadGroup01,new ThreadImplentsRunnable(),"thread-01");
Thread thread02 = new Thread(threadGroup01,new ThreadImplentsRunnable(),"thread-02")
③ 线程组的枚举
Thread[] threadList = new Thread[10];
threadGroup.enumerate(threadList);
④ main线程组的获取
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup()
4、线程间通信
线程的通信可以定义为:当多个线程共同操作共享资源时,线程间通过某种方式互相告知自己的状态,避免无效的资源争夺。通信方式有:等待-通知、共享内存、管道流。其中[等待-通知]是使用较普遍的通信方式。
Java内置锁可以使用wait()和notify()来实现”等待-通知“的通信方式。使用wait()方法以后,JVM会将当前线程加入该锁监视器的等待集合(WaitSet)。使用notify()后,JVM会唤醒该锁监视器等待集合中的第一条线程,若使用notifyAll会唤醒监视器等待集合的所有线程。
5、线程池的架构设计
Java线程的创建和销毁代价都比较高,频繁的创建和销毁线程非常低效,所以出现了线程池。线程池的好处:
- 提升性能:不需要自己创建线程,将任务交给线程池去执行,线程池能尽可能使用空闲的线程执行异步任务,对创建的线程实现复用
- 线程管理:线程池可以对线程进行有效管理,使得异步任务得到高效调度执行
① Executor:Executor是Java异步目标任务”执行者“接口,其只包含一个方法execute(Runnable command)。
② ExecutorService:ExecutorService继承Executor,其新增了submit和invoke系列方法,对外提供了异步任务的接收服务。
③ AbstractExecutorService:AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。
④ ThreadPoolExecutor:JUC线程池的核心实现类,线程池预先提供了指定数量的可重用线程,并对每个线程池都维护了一些基础数据统计,方便线程的管理和监控。
⑤ ScheduledExecutorService:继承于ExecutorService,用于完成”延时“和周期性任务的调度线程接口。
⑥ ScheduledThreadPoolExecutor:它提供了ScheduledExecutorService中的”延时执行“和”周期执行“等抽象调度方法的具体实现。
⑦ Executors是一个静态工厂类,提供了快速创建线程池的方法。
(2)Executors的使用与弊端 Java通过Executors工厂类提供了4中快捷创建线程池的方法。
方法名 | 功能简介 |
newSingleThreadExecutor() | 创建只有一个线程的线程池 |
newFixedThreadPool(int nThreads) | 创建固定大小的线程池 |
newCachedThreadPool() | 创建一个不限制线程数量的线程池,任何提交的任务都立即执行,但空闲线程会得到及时回收 |
newScheduledThreadPool() | 创建一个可定期或延时执行任务的线程池 |
- 通过newFixedThreadPool(int nThreads)创建固定大小的线程池或通过newSingleThreadExecutor()创建只有一个线程的线程池,若任务提交速度持续大于任务处理速度,会造成大量的任务等待,等待队列过大会造成内存溢出异常
- 通过newCachedThreadPool()创建一个不限制线程数量的线程池,若大量任务被启动,则需要创建大量的线程,也可能导致内存溢出异常(OOM,Out Of Memory)
- 通过newScheduledThreadPool()创建一个可定期或延时执行任务的线程池,同样会因为线程数不设限制,从而导致OOM。
(1)ThreadPoolExecutor 企业开发规范会要求使用标准的ThreadPoolExecutor构造工作线程池,其中会使用到其较重要的构造器如下:
public ThreadPoolExecutor(
int corePoolSize,//核心线程数,空闲也不会回收
int maximumPoolSize,//最大线程数
long keepAliveTime,TimeUnit unit,//线程最大空闲时长
BlockingQueue< Runnable> workQueue,//任务的排队队列
ThreadFactory threadFactory,//新线程的产生方式
RejectedExecutionHandler handler//拒绝策略
)
① 核心和最大线程数量 线程池执行器根据corePoolSize和maximumPoolSize来自动维护线程池的工作线程,当maximumPoolSize被设置为Integer.MAX_VALUE时,线程池可以接收任意数量的并发任务。corePoolSize和maximumPoolSize可以在运行过程中通过set方法动态更改。
使用线程池可以降低资源消耗,提高相应速度和线程的管理性。但是线程数配置不合理会适得其反。对于不同的任务类型将配置不同的线程数:
- IO密集型任务:IO操作时间较长会导致CPU处于空闲状态,可将核心线程数设置为CPU核数的两倍
- CPU密集型任务:CPU一直在运行,可将核心线程数设置为CPU核数
- 混合型任务:最佳线程数=((线程等待时间+线程CPU
推荐阅读
- plugin cannot be loaded for module “QtQuick“ && Could not load the Qt platform plugin “windows“
- eclipse有时候自动提示这个错误,百度了下也是懵逼,求大神
- Ansible 入门和常用模块
- 解决“在系统启动时,至少有一个服务或驱动程序产生错误”的提示
- 不能访问网络位置
- Micromedia Flash Player已终止一项可能不安全的操作解决办法
- 编程艺术0002_两数相加_解法
- win8.1中安装sql2014 0x800F0906Error while enabling Windows feature :
- 关于解决U盘“无法删除文件夹 目录不是空的”问题