当前位置: 首页 > news >正文

多线程之Thread

目录

一、多线程

1、实现线程的方式

1.1、实现Runnable接口

1.2、继承Thread类

1.3、实现Callable接口

1.4、对比

2、线程的生命周期

2.1、多线程状态源码

3、为什么不能多次调用start()方法


一、多线程

1、实现线程的方式

Java中,实现线程的方式大体上分为三种,通过实现 Runnable 接口、继承Thread 类、 实现 Callable接口。

1.1、实现Runnable接口

package com.ceam.thread;


/**
 * @author CeaM
 * 2022/08/30 20:12
 **/
public class Task implements Runnable {

    @Override 
    public void run() { 
        //TODO 在此写线程中执行的业务逻辑 
    } 
}

1.2、继承Thread

package com.ceam.thread;


/**
 * @author CeaM
 * 2022/08/30 20:00
 **/
public class ThreadTest extends Thread {

 @Override 
 public void run() { 
 
  } 
}

1.3、实现Callable接口

package com.ceam.thread.callable;

import java.util.concurrent.Callable;

/**
 * @author CeaM
 * 2022/08/30 20:08
 **/
public class CallableTest implements Callable<String> {

    @Override
    public String call() throws Exception {
        //TODO 在此写在线程中执行的业务逻辑 
        return null;
    }
}

1.4、对比

1、采用实现 Runnable、Callable 接口的方式创建多线程。

优势是:

线程类只是实现了 Runnable 接口或 Callable 接口, 还可以继承其他类。

在这种方式下, 多个线程可以共享同一个 target 对象, 所以非常适合多个相同线程来处理同一份资源的情况, 从而可以将 CPU、代码和数据分开, 形成清晰的模型, 较好地体现了面向对象的思想。

劣势是:

编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread() 方法。

2、使用继承 Thread 类的方式创建多线程

优势是:

编写简单, 如果需要访问当前线程, 则无需使用 Thread.currentThread()方法, 直接使用 this 即可获得当前线程。

劣势是:

线程类已经继承了 Thread 类, 所以不能再继承其他父类

3、Runnable 和 Callable 的区别

1Callable 规定 重写的方法是 call(),Runnable 规定( 重写的方法是 run()。2Callable 的任务执行后可返回值, 而 Runnable 的任务是不能返回值的。

3、Call 方法可以抛出异常, run 方法不可以。

4、运行 Callable 任务可以拿到一个 Future 对象, 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future 对象可以了解任务执行情况, 可取消任务的执行, 还可获取执行结果

2、线程的生命周期

  • NEW初始状态,线程被构建即new,但是还没有调用start()方法。
  • RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。处于就绪状态的线程, 只是说明此线程已经做好了准备, 随时等待 CPU 调度执行, 并不是说执行了 t.start()此线程立即就会执行,所以会看到线程一般情况下是无序执行的。当 CPU 开始调度处于就绪状态的线程时, 此时线程才得以真正执行, 即进入到运行状态。注: 就 绪状态是进入到运行状态的唯一入口, 也就是说, 线程要想进入运行状态执行, 首先必须处于就绪状态中;
  • BLOCKED阻塞状态,处于运行状态中的线程由于某种原因,暂时放弃对 CPU 的使用权, 停止执行, 此时进入阻塞状态,需要等待其他线程释放锁或者等待进入 synchronized 直到其进入到就绪状态, 才 有机会再次被 CPU 调用以进入到运行状态。
  • WAITING表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进 而进入下一个状态。
  • TIME_WAITING超时等待状态。可以在一定的时间自行返回。
  • TERMINATED终止状态,当前线程执行完毕。

 

根据阻塞产生的原因不同, 阻塞状态又可以分为三种:

1、等待阻塞:运行状态中的线程执行 wait()方法,使本线程进入到等待阻塞状态;

2、同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用), 它会进入同步阻塞状态;

3、其他阻塞:通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时, 线程重新转入就绪状态

2.1、多线程状态源码

    public enum State {
        /**
         * 线程还没有启动
         */
        NEW,

        /**
         * 可运行线程
         */
        RUNNABLE,

        /**
         * 阻塞的,可能是在等待进入同步块/方法时被阻塞的
         * WAITING 不同在于, BLOCKED 是还没有进入同步块/方法时被阻塞,WAITING 是已经进去到获取同步块的过程中了,但却获取不到锁
         */
        BLOCKED,

        /**
         * 等待,遇到 Object#wait()、Thread.join、LockSupport#park() 这些方法时,线程就会等待
         * 等待另外一个线程执行特定的操作
         * 一个线程 Object.wait() 后,需要等待另外一个线程执行同一个 Object 的 notify()
         * 或者线程执行 thread1.join(),等待 thread1 来打断
         */
        WAITING,
        TIMED_WAITING,

        /**
         * 终止线程
         */
        TERMINATED;
    }

注意:

// The VM can handle all thread states
// Vm 可以处理所有的线程状态

3、为什么不能多次调用start()方法

    /**
     * 该方法可以创建一个新的线程出来
     * 启动线程,线程状态从 NEW 进入 RUNNABLE
     *
     * @throws IllegalThreadStateException 若线程已经启动
     * @see #run()
     * @see #stop()
     */
    public synchronized void start() {
        // 如果已经初始化,抛异常
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        // 将当前线程加入到所在的线程组,记录为活跃线程
        group.add(this);

        // started 是个标识符,我们在初始化一些东西的时候,经常这么写
        boolean started = false;
        try {
            // 这里会创建一个新的线程,执行完成之后,新的线程进入Runnable状态
            start0();
            // 这里执行的还是主线程
            started = true;
        } finally {
            try {
                // 如果失败,把线程从线程组中删除
                if (!started) {
                    group.threadStartFailed(this);
                }
                // 这里的 catch 捕捉也是值得我们学习的,我们在工作中 catch 时也应该多用 Throwable,少用 Exception
                // 比如对于异步线程抛出来的异常,Exception 是捕捉不住的,Throwable 却可以
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

注意:

当调用start方法时,线程的状态会从NEW改为RUNNABLE,也就是threadStatus不再为初始化状态0,故再次调用start()方法就会抛出异常。Vm 可以处理所有的线程状态!!!

4、Thread类常用的方法:

  • Thread.activeCount():这个方法用于返回当前线程的线程组中活动线程的数量,返回的值只是一个估计值,因为当此方法遍历内部数据结构时,线程数可能会动态更改。)。
  • Thread.checkAccess(): 检验当前正在执行的线程是否有权限修改thread的属性,这个方法我们一般不自己进行调用,Thread类的set方法在进行属性修改时都会先调用这个方法。
  • Thread.currentThread():获取当前正在运行的线程。
  • Thread.dumpStack():输出线程栈,一般在debug的时候调用。
  • Thread.enumerate(Thread tarray[]):??使用场景。
  • Thread.getAllStackTraces():获取系统中所有线程的线程栈信息。
  • thread.getName():获取线程的名字。
  • thread.getPriority():获取线程的优先级。
  • thread.getStackTrace():获取堆栈信息。
  • thread.getState():获取线程状态。
  • thread.getThreadGroup():获取线程所在线程组。
  • thread.interrupt():使得指定线程中断阻塞状态,并将阻塞标志位置为true。
  • thread.interrupted():测试当前线程是否被中断。
  • thread.isAlive():判断线程是否还存活着。
  • thread.isDaemon():判断线程是否是守护线程。
  • thread.join():在当前线程中加入指定线程,使得当前线程必须等待指定线程运行结束之后,才能结束。可以理解成线程插队、等待该线程终止。
  • Thread.sleep(long):强制线程睡眠一段时间。
  • thread.start():启动一个线程。
  • thread.setName(name):设置线程的名字。
  • thread.setPriority(priority):设置线程的优先级。
  • thread.setDaemon(true):将指定线程设置为守护线程。
  • thread.yield():使得当前线程退让出CPU资源,把CPU调度机会分配给同样线程优先级的线程。
  • object.wait()、object.notify()、object.notifyAll():Object类提供的线程等待和线程唤醒方法。

相关文章:

  • Selenium自动化测试框架
  • 四年时间,从一个浑浑噩噩的程序员到csdn博客专家的成长之路
  • Spring Boot 9 :详细描述Spring Boot + Vue项目部署过程:Centos为例(重点)
  • 医疗项目业务介绍
  • 猿创征文 | Git的良心教程
  • 【线性代数基础进阶】特征值和特征向量-补充+练习
  • dc_shell的report_xx和查找pin或cell的input/output [all_fanin/out]
  • (LeetCode C++)盛最多水的容器
  • vue3 eventBus订阅发布模式
  • Maven的安装与配置以及注意事项
  • 【JavaWeb】JavaScript
  • MySQL常见操作
  • [ C++ ] STL_list 使用及其模拟实现
  • 树状数组笔记
  • 【ffmpeg】SDL视频显示
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • echarts花样作死的坑
  • Java超时控制的实现
  • js 实现textarea输入字数提示
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • Redis 懒删除(lazy free)简史
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • Swift 中的尾递归和蹦床
  • 初识 webpack
  • 创建一种深思熟虑的文化
  • 机器学习 vs. 深度学习
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 前嗅ForeSpider教程:创建模板
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 如何解决微信端直接跳WAP端
  • 使用putty远程连接linux
  • 事件委托的小应用
  • 突破自己的技术思维
  • No resource identifier found for attribute,RxJava之zip操作符
  • 1.Ext JS 建立web开发工程
  • ​Z时代时尚SUV新宠:起亚赛图斯值不值得年轻人买?
  • !! 2.对十份论文和报告中的关于OpenCV和Android NDK开发的总结
  • #include到底该写在哪
  • #中国IT界的第一本漂流日记 传递IT正能量# 【分享得“IT漂友”勋章】
  • (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (C++)八皇后问题
  • (day6) 319. 灯泡开关
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (二十三)Flask之高频面试点
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (心得)获取一个数二进制序列中所有的偶数位和奇数位, 分别输出二进制序列。
  • (原創) 如何動態建立二維陣列(多維陣列)? (.NET) (C#)
  • .net core webapi 部署iis_一键部署VS插件:让.NET开发者更幸福
  • .NET Core 网络数据采集 -- 使用AngleSharp做html解析
  • .NET 服务 ServiceController
  • .NET 中 GetProcess 相关方法的性能
  • .net下简单快捷的数值高低位切换