我知道的是我一无所知
谢谢拉钩教育张雷老师《32个java面试必考点》及王磊老师《java源码剖析34讲》还有各大佬们的分享,
进程与线程
进程与线程,这个真的是老生常谈到你不知道他是多么的重要,由点及面可以说到天荒地老,不要紧张,有时候天荒地老 眨眼就到
线程:程序执行的最小单位,共享进程的数据空间,当然作为独立的thing也有自己的空间
进程
程序动态运行的实例,操作系统使用task_struct结构体标识进程
task_struct
- 标识符:唯一标识
- 状态:R(running)运行,S(sleeping)可中断的休眠,D(disk sleep)不可中断的休眠,需“唤醒”,T(stopped)停止,t(tracing stop)追踪,X(dead)死亡,Z(zombie)僵死
- 上下文数据:进程执行时处理器的寄存器中的数据
- 程序计数器:进程下一次要执行指令的地址
- 优先级:进程被执行的先后顺序问题
- IO状态信息:显示的I/O请求,分配给进行的I/O设备和被进程使用的文件列表等
- 记账信息:有处理器的时间总和、记账号等
详情请咨询:https://blog.csdn.net/bit_clearoff/article/details/54292300
指令
我们常见的亲切的top大哥
ps -ef | pid 查询all进程信息
ps aux:查看进程详细信息
当然还有很多,k8s、docker也有索性使用起来大同小异
线上问题排查pid也是起了很大的作用,这块完全可以另起一篇了
优先级
ps -l或者top命令
优先级PRI:可被执行的优先级、值越小优先级越高
真实进程的PRI=PRI+nice,nice可修改
未运行的进程 nice -n setValue 程序,如 nice -n -10 ./test
已经运行的进程renice setValue -p pid,如 renice -10 -p 4500
地址空间
进程地址空间是虚拟地址,虚拟地址和物理地址页表关联
写时复制
父子进程修改全局变量时,物理内存空间将重新分配内存保存父子进程修改后的值并刷新页表,所以虚拟内存地址不变值不一定相同
摘自:https://blog.csdn.net/ksaila/article/details/83794568
进程间通信 :
1、管道pipe:管道是半双工通信,数据在有亲缘关系(父子)进程间单向流动
2、命名管道FIFO:半双工通信,无亲缘关系也可以通信
3、消息队列messageQueue:双向通信,全局链表,节点保存数据报的类型和内容,消息队列标识符标记
4、共享存储sharedMemory:由一个进程创建,将同一物理内存映射到不同进程虚拟地址空间中,这样就不需要用户态与内核态的来回切换还有数据拷贝(是不是很ok),直接被多个进程访问的内存也是危险,你争我抢、完整的人进去 四分五裂的出来,还是要保证原子性,如何实现?信号量、互斥锁,牛奶咖啡冰淇淋了解一下
5、信号量semaphore:无论如何花里胡哨,说计数器多好,东西就这么少、你想要问一下信号量有没有资格!是的,控制多个进程对共享资源的访问和同一进程不同线程同步的利器,数组元素+1 -1 了解一下
6、套接字socket:不同机器间的进程通信,机器哦不是进程
7、信号sinal:进程间、同一进程不同线程间同步手段
线程
像一条条赛道,什么都没有来回瞎跑,马路还有个地标呐!线程用状态标记一下吧
创建线程
1、继承Thread并重写run方法,创建子类对象并调用start,这个讲解的时候用到挺多的
public class Thread1 {
public static void main(String[] args) {
//创建一个新线程
ThreadDemo1 thread1 = new ThreadDemo1();
//为线程设置名称
thread1.setName("线程一");
//开启线程
thread1.start();
}
}
public class ThreadDemo1 extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
2、实现Runnable接口的类实现run方法,类对象在创建Thread时作为参数传入,调用start方法
public class Thread2 {
public static void main(String[] args) {
Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
//创建一个新线程,注意参数
Thread thread2 = new Thread(new ThreadDemo2());
//为线程设置名称
thread2.setName("线程二");
//开启线程
thread2.start();
}
}
public class ThreadDemo2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
}
}
3、实现callable接口,重写有返回值可抛异常的call方法
public class Thread3 {
public static void main(String[] args) throws Exception {
//创建FutureTask的对象,参数
FutureTask<String> task = new FutureTask<String>(new ThreadDemo3());
//创建Thread类的对象,参数
Thread thread3 = new Thread(task);
thread3.setName("线程三");
//开启线程
thread3.start();
//获取call()方法的返回值,即线程运行结束后的返回值
String result = task.get();
System.out.println(result);
}
}
class ThreadDemo3 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
return Thread.currentThread().getName()+":"+"返回的结果";
}
}
start与run
start启动新线程,状态为runnable、要为线程分配系统资源、调度线程运行并执行run方法
run实际要做的工作,runnable到running
锁
先说一下java变量吧?这篇博客眼看着越来越庞大,这样是不是不好
类变量、静态变量:独立于方法之外的,嗯?什么是方法之外,我想送你离开,用static修饰这样是不是好懂一些,关于static也可以开篇来一个,累了不写了
实例变量、成员变量:独立方法之前外,诶?这个不和上面的一样吗?不急 我还没有写完,自然没有static修饰,否则岂不是要怀疑人生
局部变量:声明在方法、构造方法、语句块中,栈上分配
每个对象都有一把锁,锁定类实例或类对象
加锁:synchronized
1、方法上,同步方法,锁定类实例
同一对象只有一把锁,不同的对象是互不影响的
2、类变量、类方法上,static
锁定类对象,属于类的,在jvm中独一份,访问这个类相关的方法前都要先问这个锁闲不闲
如果类中使用了synchronized定义非静态方法,影响all使用这个方法的方法
如synchronized定义静态方法,影响类中all使用该方法的方法
3、同步代码块
锁释放:jvm把该线程对应的本地内存中共享变量刷新到主内存
获取锁:jvm将线程对应的本地内存置为无效,被锁保护的临界区代码从主内存读取变量
用前先想一下;当前是类还是对象,锁在哪
lock:略
状态
说这块那铁定少不了那张编程界流传已久的——图
简要解释一下、图其实很明晰了:
就绪状态:可运行,等待获取CPU使用权
调用start进入
线程sleep结束,其他线程join结束,线程拿到对象锁 进入
当前线程时间片用完,调用当前线程yield,当前线程进入
阻塞状态:某种原因放弃cpu使用权,暂停运行找到进入就绪态
等待阻塞:wait,等待队列
同步阻塞:想要得到的同步锁被别的线程占用,jvm将线程放入锁池
其他阻塞:运行线程执行sleep或join 或 I/O请求 ,jvm把线程置为阻塞
这张图也是很形象了,源于图上的连接,博客的访问量、好评都挺高的;同步、等待队列这张图说的很明白了
同步队列:想要争夺锁的线程,线程唤醒之后进入的是同步队列去争夺锁
如获得锁进入runnable状态,否则blocked状态
https://blog.csdn.net/qq_27717921/article/details/77418128
那就多看几遍,刻意练习
也找到一张图,谢谢作者大大 同时也谢谢这么努力的自己个儿
源自: https://www.cnblogs.com/thinfog/p/11250913.html 谢谢
线程调度:下面的这些台词多少在zk、nginx、kafka上也都是排面,所以学会了是不是有些以一敌百的小确幸
时间片轮询、先来先服务、优先级调度、多级反馈队列及高响应比优先调度
方法解释
- sleep当前线程调用,让出CPU时间片、不释放锁,给其他线程执行机会
- yield当下线程调用,放弃cpu时间片不释放锁,运行态变就绪态,优先级高的执行机会大
- join/join(long millis)当前线程调用其他线程的join(),当前线程进入waiting/timed_waiting态,不释放锁,线程执行完或时间到了当前线程一般会进入runnable,也可能blocked(join就wait实现)
- obj.wait当前线程调用对象的wait方法,必须在synchronized中,当前线程释放锁进入等待队列,靠notify或notifyAll唤醒或wait(long timeout) 等timeout后自动唤醒
- locksupport.park / lockSupport.parkNanos(long nanos) / lockSupport.parkUntil(long deadlines)当前线程进入waiting / timed_waiting,通过lockSupport.unpark(Thread thread)唤醒
- notify、notifyAll必须在synchronized中
https://blog.csdn.net/ywlmsm1224811/article/details/94022647
static class MyThread1 implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println("thread1 start");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1 end");
}
}
}
static class MyThread2 implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println("thread2 start");
obj.notify();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2 end");
}
}
}
static class MyThread1 implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println("thread1 start");
Thread.yield();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1 end");
}
}
}
wait让出CPU时间片释放锁
yield涉及到优先级
线程创建时,子继承父的优先级
线程创建后,可通过setPriority改变,优先级是0-10间整数,正比
线程池
fixedThreadPool和singleThreadPool:容许请求队列长度为Integer.MAX_VALUE,可能会堆积大量请求,导致OOM
cachedThreadPool和scheduledThreadPool:容许创建线程数量为integer.MAX_VALUE,可能会创建大量线程、导致OOM
//corePoolSize线程池常驻核心线程数,设置为0无任务时销毁线程池,>0无任务时池中线程数==该值,据业务调整
//线程池最大可创建的线程数,必须>0,>corePoolSize
//线程存活时间,线程池空闲且超过该时间 多余线程会销毁
//存活时间的单位,配合keepAliveTime使用
//线程池执行的任务队列,池中线程都在处理任务新来的任务进入该队列等等执行
//线程的创建工厂,使用时可以不指定使用默认工厂
//拒绝策略,缓存任务队列满且不能创建新线程,使用何种策略拒绝
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
// maximumPoolSize 必须大于 0,且必须大于 corePoolSize
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
threadPoolExecutor扩展
重写beforeExceute和afterExecute
public class ThreadPoolExtend {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 线程池扩展调用
MyThreadPoolExecutor executor = new MyThreadPoolExecutor(2, 4, 10,
TimeUnit.SECONDS, new LinkedBlockingQueue());
for (int i = 0; i < 3; i++) {
executor.execute(() -> {
Thread.currentThread().getName();
});
}
}
/**
* 线程池扩展
*/
static class MyThreadPoolExecutor extends ThreadPoolExecutor {
// 保存线程执行开始时间
private final ThreadLocal<Long> localTime = new ThreadLocal<>();
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
/**
* 开始执行之前
* @param t 线程
* @param r 任务
*/
@Override
protected void beforeExecute(Thread t, Runnable r) {
Long sTime = System.nanoTime(); // 开始时间 (单位:纳秒)
localTime.set(sTime);
System.out.println(String.format("%s | before | time=%s",
t.getName(), sTime));
super.beforeExecute(t, r);
}
/**
* 执行完成之后
* @param r 任务
* @param t 抛出的异常
*/
@Override
protected void afterExecute(Runnable r, Throwable t) {
Long eTime = System.nanoTime(); // 结束时间 (单位:纳秒)
Long totalTime = eTime - localTime.get(); // 执行总时间
System.out.println(String.format("%s | after | time=%s | 耗时:%s 毫秒",
Thread.currentThread().getName(), eTime, (totalTime / 1000000.0)));
super.afterExecute(r, t);
}
}
}
线程池的工作流程:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 当前工作的线程数小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 创建新的线程执行此任务
if (addWorker(command, true))
return;
c = ctl.get();
}
// 检查线程池是否处于运行状态,如果是则把任务添加到队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再出检查线程池是否处于运行状态,防止在第一次校验通过后线程池关闭
// 如果是非运行状态,则将刚加入队列的任务移除
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果线程池的线程数为 0 时(当 corePoolSize 设置为 0 时会发生)
else if (workerCountOf(recheck) == 0)
addWorker(null, false); // 新建线程执行任务
}
// 核心线程都在忙且队列都已爆满,尝试新启动一个线程执行失败
else if (!addWorker(command, false))
// 执行拒绝策略
reject(command);
}
addWorker(Runnable firstTask,boolean core)
firstTask线程应首先执行的任务,没有则设null
core是否可创建线程的阈值,true将corePoolSize作为最大值,false使用maximumPoolSize作为最大值
submit与execute均执行线程池任务
submit属于ExecutorService接口方法,可接收线程池执行的返回值Future<>
execute属于Executor接口方法,无返回值
拒绝策略
abortPolivy终止策略,线程池抛异常并终止执行,默认的
callerRunsPolicy任务交给当前线程执行
discardPolicy忽略此任务(最新的任务)
discardOldestPolicy忽略最早的任务(最先加入队列的任务)
自定义拒绝策略
新建 RejectedExecutionHandler 对象,然后重写它的 rejectedExecution() 实现自己的逻辑
携程、抱歉,是协程:比线程更轻量级的存在,特殊函数、协程间串行执行
在用户态调整,切换由程序决定进行硬件上下文的切换,更加轻量化、切换代价自然比线程上下文切换低很多
进程切换时机操作系统的策略,含全局目录、内核栈、硬件上下文、切换内容存内存中,so用户态到内核态到用户态
线程切换由操作系统决定,含内核栈和硬件上下文,切换内容保存在内核栈中
谢谢亲们,今天呐咱们就到这,散了吧
https://www.jianshu.com/p/6dde7f92951e
https://www.cnblogs.com/li666/p/11139968.html
https://www.runoob.com/java/java-variable-types.html
https://blog.csdn.net/u013262534/article/details/81676657
https://blog.csdn.net/zhaohong_bo/article/details/89552188
https://blog.csdn.net/qq_27717921/article/details/77418128
https://blog.csdn.net/zjy15203167987/article/details/82531772
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=1#/detail/pc?id=3