剑指JUC原理-2.线程

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring源码、JUC源码
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

创建和运行线程

直接使用Thread

// 创建线程对象
Thread t = new Thread() {
 public void run() {
 // 要执行的任务
 }
};
// 启动线程
t.start();

--------------------------------------------------
    
// 构造方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") {
 @Override
 // run 方法内实现了要执行的任务
 public void run() {
 log.debug("hello");
 }
};
t1.start();

输出:
19:19:00 [t1] c.ThreadStarter - hello

使用Runnable配合Thread

其特点是:把“线程”和“任务”(要执行的代码)分开

Thread代表线程

Runnable代表可运行的任务(线程要执行的代码)

Runnable runnable = new Runnable() {
 public void run(){
 // 要执行的任务
 }
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start(); 

--------------------------------------------------
    
// 创建任务对象
Runnable task2 = new Runnable() {
 @Override
 public void run() {
 log.debug("hello");
 }
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();

输出:
19:19:00 [t2] c.ThreadStarter - hello

在java8以后,可以使用lambda来精简代码,快捷键是选中Runnable alt + enter

Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();

之所以Runnable能使用lambda,主要是Runnable接口有@FunctionalInterface,该注解只允许存在一个接口方法。

Thread和Runnable的关系(源码)

在这里插入图片描述

通过定位源码发现 Runnable的实现方式,其实走的还是run方法,有Runnable的还是优先执行Runnable的run方法,而Thread本身的实现其实就是重写了run方法。

总结:
  • Thread是把线程和任务合并在了一起,Runnable是把线程和任务分开了
  • 用 Runnable 更容易与线程池等高级 API 配合
  • 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

FutureTask配置Thread

FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况

// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {
 log.debug("hello");
 return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);

输出:
19:22:27 [t3] c.ThreadStarter - hello
19:22:27 [main] c.ThreadStarter - 结果是:100

FutureTask代码分析

在这里插入图片描述

其中get方法是一个主线程阻塞同步等待task执行完毕的结果

多线程同时执行

public static void main(String[] args) {
        new Thread(() -> {
            while(true) {
                log.debug("running");
            }
        },"t1").start();
        new Thread(() -> {
            while(true) {
                log.debug("running");
            }
        },"t2").start();
    }

实际的输出结果是交替执行,谁先谁后并不由我们来控制,是由底层的任务调度器来决定的。

查看进行线程的方法

windows

任务管理器可以查看进程和线程数,也可以用来杀死进程

在这里插入图片描述

tasklist 查看所有进程

taskkill 进程号 杀死某个进程

java

jps 命令查看所有 Java 进程

jstack 查看某个 Java 进程(PID)的所有线程状态

linux

ps -fe 查看所有进程
ps -fT -p 查看某个进程(PID)的所有线程
kill 杀死进程
top 按大写 H 切换是否显示线程
top -H -p 查看某个进程(PID)的所有线程

线程运行之原理

栈与栈帧

Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟
机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

以debug的方式来介绍栈帧

	public static void main(String[] args) {
        method1(10);
    }

    private static void method1(int x) {
        int y = x + 1;
        Object m = method2();
        System.out.println(m);
    }

    private static Object method2() {
        Object n = new Object();
        return n;
    }

我们对method1 打上断点进行debug

在这里插入图片描述

首先可以看到main先进入了栈帧中,其Variables中 有其参数

当继续进行的时候,此时method1进入栈帧,同时其Variables 包含其入参

在这里插入图片描述

继续执行,其Variables 包含其局部变量

在这里插入图片描述

继续执行,此时method2进入栈帧

在这里插入图片描述

然后是返回n

在这里插入图片描述

由于栈的数据结构是先进先出的,当执行完毕后,会对method2进行释放

在这里插入图片描述

以上就是栈帧的整个执行流程了。

图解线程流程

在这里插入图片描述

最开始是执行一个类加载,将类加载到java虚拟机中,加载的位置是将字节码放置在方法区

类加载完成后,java虚拟机就会启动一个main的主线程,给主线程分配一块栈内存,此时线程就交给任务调度器调度执行

如果cpu分配给我们的主线程了

在这里插入图片描述

虚拟机会给main方法分配一块栈帧内存

在这里插入图片描述

当然每个栈内存中还存在一个程序计数器的组件,它是每个线程私有的,记录了我当前该执行哪行代码,cpu就向程序计数器要 是什么

在这里插入图片描述

在这里插入图片描述

局部变量表其实是创建时其实就已经分配好了,不是运行到某行代码时才分配内存

其中method1栈帧中 x = 10,并且相应的语句也会逐渐的到程序计数器中

在这里插入图片描述

在这里插入图片描述

然后的流程其实就是和debug一样,逐层的释放掉了

多线程运行原理

public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                method1(20);
                // 断点模式 要选择 thread 不要选择 all
            }
        };
        t1.setName("t1");
        t1.start();
        method1(10);
    }

    private static void method1(int x) {
        int y = x + 1;
        Object m = method2();
        System.out.println(m);
    }

    private static Object method2() {
        Object n = new Object();
        return n;
    }

此时的断点模式要选择 Thread

在这里插入图片描述

其实本质上和单线程是一样的,不同点在于,每个线程都有一个自己私有的栈,每个栈里面还是个单线程一样,但是线程切换的话,会保存当前的操作。其实最本质的就是要理解,每个栈内存是相互独立的。

线程上下文切换

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念
就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

在这里插入图片描述

相关推荐博客

👉👉👉 剑指JUC原理-1.进程与线程-CSDN博客

👉👉👉 剑指JUC原理-3.线程常用方法及状态-CSDN博客

👉👉👉 剑指JUC原理-4.共享资源和线程安全性-CSDN博客

👉👉👉 剑指JUC原理-5.synchronized底层原理-CSDN博客

👉👉👉 剑指JUC原理-6.wait notify-CSDN博客

👉👉👉 剑指JUC原理-7.线程状态与ReentrantLock-CSDN博客

👉👉👉 剑指JUC原理-8.Java内存模型-CSDN博客

👉👉👉 剑指JUC原理-9.Java无锁模型-CSDN博客

👉👉👉 剑指JUC原理-10.并发编程大师的原子累加器底层优化原理(与人类的优秀灵魂对话)-CSDN博客

👉👉👉 剑指JUC原理-11.不可变设计-CSDN博客

👉👉👉 剑指JUC原理-12.手写简易版线程池思路-CSDN博客

👉👉👉 剑指JUC原理-13.线程池-CSDN博客

👉👉👉 剑指JUC原理-14.ReentrantLock原理-CSDN博客

👉👉👉 剑指JUC原理-15.ThreadLocal-CSDN博客

👉👉👉 剑指JUC原理-16.读写锁-CSDN博客

👉👉👉 剑指JUC原理-17.CompletableFuture-CSDN博客

👉👉👉 剑指JUC原理-18.同步协作-CSDN博客

👉👉👉 剑指JUC原理-19.线程安全集合-CSDN博客

👉👉👉 剑指JUC原理-20.并发编程实践-CSDN博客

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃芝士的土豆倪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值