【JUC】JAVA线程小结

发布时间:2024年01月13日


在这里插入图片描述

前言——阅读10-20分钟

本文仅对Java的线程使用学习做个小结,不涉及过多底层原理和锁的实现,

🎆1.创建和运行线程

Thread

  • run任务创建和start执行都由线程直接操作
// 创建线程对象
Thread t = new Thread() {
   public void run() {
	// 要执行的任务
    };
	// 启动线程
t.start();

Runable

  • 任务由Runable创建
  • 任务由Thread线程执行
  • 达到解耦目录,执行结果是一样的
Runnable runnable = new Runnable() {
	public void run(){
// 要执行的任务
	}
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start();

FutureTask

  • FutureTask的作用和Runable一样
  • FutureTask有返回值, Runable 无返回值
  • 可以用FutureTask task 接收线程执行的返回值
  • task.get()获取返回值
// 创建任务对象
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);

多个线程运行方式

  • 交替执行
  • 谁先谁后,不由我们控制,由CPU决定

📣2.不同操作系统查看进程线程的方法

windows

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

  • tasklist 查看进程
  • taskkill 杀死进程

linux

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

Java命令行

  • jps 命令查看所有 Java 进程
  • jstack 查看某个 Java 进程(PID)的所有线程状态
  • jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

jconsole 远程监控配置

  • 需要以如下方式运行你的 java 类
java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -
Dcom.sun.management.jmxremote.authenticate=是否认证 java类
  • 修改 /etc/hosts 文件将 127.0.0.1 映射至主机名

如果要认证访问,还需要做如下步骤

  • 复制 jmxremote.password 文件
  • 修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写
  • 连接时填入 controlRole(用户名),R&D(密码

🚀3.java线程运行原理

栈与栈帧

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

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

线程上下文切换(Thread Context Switch)

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

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

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

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

开启IDEA的debugger模式,这里每一行对应一个栈帧

  • read方法
  • main方法
  • 先进后出,先运行的在栈底

图片.png
一个线程对应一个方法栈

  • 线程t1和t2

图片.png

👇4.常见的线程API

图片.png图片.png图片.png图片.png

不推荐的方法

图片.png

🐳5.Runable和Thread的run的区别

  1. 通过组合的方式调用run,从源码角度实际都是同一个run方法
  • 源码中Runable重载run,再传参给Thread,在Thread的中判断 Runable 不空,优先调用 Runable 的方法
  • 否则就是Thread的run方法,但其实Runable的run方法的内部还是Thread的run方法
  1. 直接在Thread重载run方法,则运行时运行重载的方法

为什么有 Thread 还要有 Runable

因为有的场景下我们需要把任务和执行两者分开解耦

  • Thread创建run任务并start执行,任务和执行都耦合在Thread对象中
  • Runable用来创建任务,以传参的方式将 Runable所带的任务传入Thread,然后让Thread执行就实现了任务和执行的解耦

有人会问什么情况下需要这种分离的玩意?

  • 消息队列: 生产者消费者就是执行者,队列里面的就是任务
  • 凡是需要创建任务和执行任务分离的场景,都可以尝试使用进行解耦

start和run的区别

  1. 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  2. 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
  3. start并不会马上执行,只是让线程进入就绪态,只有他抢到CPU才能执行

🍑6.sleep和yield区别

Sleep
  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行,而是进入 Runnable 就绪状态
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
  5. 不会释放锁,只跟线程相关
Yield
  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
    2. 具体的实现依赖于操作系统的任务调度器

🌯7.sleep和wait区别

  • 涉及到锁,此篇只涉及线程,在后篇补充

?8.wait和await区别

  • 涉及到锁,此篇只涉及线程,在后篇补充

📢9.Join方法

为什么需要Join
  • 因为多线程是并发执行的,每个执行完成的顺序是不能确定的。
  • 意思如果有线程调用 t2.join()并运行到t2.join()这个位置会阻塞,直到t2线程执行结束,当前线程才能继续往下执行
private static void test2() throws InterruptedException {
	Thread t1 = new Thread(() -> {
		sleep(1);
		r1 = 10;
	});
	Thread t2 = new Thread(() -> {
		sleep(2);
		r2 = 20;
	});
	long start = System.currentTimeMillis();
	t1.start();
	t2.start();
	t1.join();---- // 运行到此卡住,直到t1执行完
	t2.join();-----// 运行到此卡住, 直到t2执行完
	long end = System.currentTimeMillis();
	log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}

💞10.interrupt

interrupt打断线程分两种情况:

  • 打断正常运行的线程
  • 打断sleep,wait,join 已阻塞线程

打断 sleep,wait,join已阻塞线程

首先声明不使用stop()打断线程的原因

  • stop()打断线程会直接杀死线程,不会给线程释放锁dd料理后事的机会
  • 资源得不到释放

如下例子就是可以处理后事线程

  • sleep,wait,join在这里可以看成是一样的阻塞态
  • (当然三者的阻塞肯定在细节上有不同,但是这里只要是阻塞就行了)
class TPTInterrupt {
        private Thread thread;
        public void start(){
            thread = new Thread(() -> {
                while(true) {
                    Thread current = Thread.currentThread();
                    if(current.isInterrupted()) {
                        log.debug("料理后事");
                        break;
                    }
                    try {
                        Thread.sleep(1000);
                        log.debug("将结果保存");
                    } catch (InterruptedException e) {
                        current.interrupt();

                    }
// 执行监控操作
                }
            },"监控线程");
            thread.start();
        }
        public void stop() {
            thread.interrupt();
        }
    }
    
--------------------------------------------------


TPTInterrupt t = new TPTInterrupt();
t.start();
Thread.sleep(3500);
log.debug("stop");
t.stop();

主要依赖这三个函数的特点"处理后事"

  • 因为sleep,wait,join也会设置打断标记
  • 而处理后事的判断条件就是打断标记
  • 通过这个标记来手动做return结束线程
  • 而不是直接stop杀死进程

图片.png图片.png

🐴11.主线程和守护线程

什么是守护线程

  • 默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。
  • 有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

比如

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求,直接结束

将线程设置为守护线程

Thread t1 = new Thread(() -> {
log.debug("开始运行...");
sleep(2);
log.debug("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);----------//设置为守护线程
t1.start();

🤙12.操作系统线程的5种状态

图片.png

😜13.Java线程的6种状态

图片.png

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换详述
  • TERMINATED 当线程代码运行结束

本章的重点在于掌握

  • 线程创建
  • 线程重要 api,如 start,run,sleep,join,interrupt 等
  • 线程状态
  • 应用方面
    • 异步调用:主线程执行期间,其它线程异步执行耗时操作
    • 提高效率:并行计算,缩短运算时间
    • 同步等待:join
    • 统筹规划:合理使用线程,得到最优效果
  • 原理方面
    • 线程运行流程:栈、栈帧、上下文切换、程序计数器
    • Thread 两种创建方式 的源码
  • 模式方面
    • 终止模式之两阶段终止
  • 6种状态

在这里插入图片描述

文章来源:https://blog.csdn.net/imbzz/article/details/135492344
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。