剑指JUC原理-2.线程

简介: 剑指JUC原理-2.线程

创建和运行线程


直接使用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 频繁发生会影响性能

目录
相关文章
|
2月前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
116 29
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
26天前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
28 0
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
121 6
【Java学习】多线程&JUC万字超详解
|
26天前
|
Java 编译器 程序员
【多线程】synchronized原理
【多线程】synchronized原理
45 0
|
26天前
|
Java 应用服务中间件 API
nginx线程池原理
nginx线程池原理
24 0
|
2月前
|
存储 缓存 Java
JAVA并发编程系列(11)线程池底层原理架构剖析
本文详细解析了Java线程池的核心参数及其意义,包括核心线程数量(corePoolSize)、最大线程数量(maximumPoolSize)、线程空闲时间(keepAliveTime)、任务存储队列(workQueue)、线程工厂(threadFactory)及拒绝策略(handler)。此外,还介绍了四种常见的线程池:可缓存线程池(newCachedThreadPool)、定时调度线程池(newScheduledThreadPool)、单线程池(newSingleThreadExecutor)及固定长度线程池(newFixedThreadPool)。
|
2月前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
28 0
|
22天前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
36 1
C++ 多线程之初识多线程
|
6天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
11 3