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

【线程】Java线程操作

【线程】Java线程操作

      • 一、启动线程
        • 1.1 run()和start()的区别
      • 二、终止线程
      • 三、等待线程
      • 四、线程的状态

一、启动线程

Java中通过start()方法来启动一个线程,其次我们要着重理解start()和run()的区别。

1.1 run()和start()的区别

我们通过一份代码来进行观察:

public class ThreadDemo1  {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()-> {
            while(true){
            System.out.println("hello 000");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        //第一次运行start()
        t.start();
        //第二次运行run()
        //t.run()
        while (true){
            System.out.println("hello 111");
            Thread.sleep(1000);
        }
        
    }
}

在这里插入图片描述
从结果上来看,用start() 方法,两个线程都正常运行了(main线程和t线程);而使用run()方法,似乎只有一个线程在正常运行。
我们可以用Jconsole来查看一下:
在这里插入图片描述
可以发现,使用start()方法真正创建出了线程,而使用run()方法只是在main线程里执行t线程对象的逻辑,并没有真正创建线程。那么我们可以这样理解:** start()方法用于创建线程,系统在合适的时机调用run方法,run()方法用于执行线程内部的逻辑。**

二、终止线程

想要终止一个线程,其实是需要里外配合的。具体来说,比如我们想要在main线程内控制t线程,我们就需要在t线程中引入一个标志位,用来控制线程。同时,我们要能在main线程中能够控制这个标志位。接下来看示例:

  1. 手动设置标志位
public class ThreadDemo1  {
    private static boolean isQuit =false;
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()-> {
            while(!isQuit){
            System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        //这个时间需要大于t线程中的休眠时间
        //避免因线程的随机调度使得t线程还没启动,标志位就被更改
        Thread.sleep(2000);
        isQuit=true;
    }
}

运行结果:
在这里插入图片描述
这里还有一个小问题:
在这里插入图片描述
如果我们把isQuit(自定义的标志位)写到main方法内部作为一个局部变量,此时就会编译报错。
这是因为λ表达式在进行变量捕获时,对于外部定义的局部变量,要求是final或者是effectively final类型的。
在这里插入图片描述
那为什么写成成员变量就可以了呢,此时是走了另一条语法规则,λ表达式/内部类可以访问外部类的成员变量,这件事情不受λ表达式变量捕获的限制。

但是我们认为,这种手动设置标志位的方式不够优雅,Thread类呢,提供了一种更加优雅的方法:interrupt()

  1. 使用Thread类提供的interrupt方法
public class ThreadDemo1  {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()-> {
            while(!Thread.currentThread().isInterrupted()){
            System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        //这个时间需要大于t线程中的休眠时间
        //避免因线程的随机调度使得t线程还没启动,标志位就被更改
        Thread.sleep(2000);
        t.interrupt();
    }
}
  • Thread.currentThread()用于获取当前线程的引用,如果是继承Thread,那么可以用this引用,但如果是实现Runnable或者是lambda表达式,就只能使用该方法来获取当前线程的引用。
    在这里插入图片描述
    此处出现了一个比较奇怪的错误 : 明明已经报错了,怎么还没停下来。
    这里其实是sleep搞的鬼。
    此处线程处于这种休眠状态,调用interrupt()就会提前唤醒这个线程。线程被提前唤醒,会做两件事:
    (1)抛出 InterruptedException异常,被catch捕获到;
    (2)清除Thread对象的标志位,把标志位继续设为false。
    所以此时线程没有真正停止。而想要使这个线程停止,只需要做出一点小小的改进:在catch中写入break:
try {
      Thread.sleep(1000);
} catch (InterruptedException e) {
      break;
}

此时思考一个问题,那么我手动设置标志位,为什么不会有这样的问题?
这是因为,手动设置的标志位并不是Thread对象的属性,只是当前类的一个成员变量。即使线程被提前唤醒,也是不能去改变这里手动设置的标志位的。

Java这里对于线程的终止采用的是一种“软性”操作,即需要线程配合才能终止。操作系统的API提供了强行终止线程的操作,但这件事往往弊大于利,Java中并没有引入这样的强制性方法。

三、等待线程

由于多个线程的执行顺序是不确定的(随机调度,抢占式执行),所以我们的有些目的就难以实现。但是我们可以通过某些api,来影响线程的执行顺序,比如可以让一个线程来等待另一个线程。

public class ThreadDemo1  {
    public static void main(String[] args) throws InterruptedException {

        Thread t=new Thread(()-> {
            for(int i=0;i<10;i++){
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException();
                }
            }
        });
        t.start();

        t.join();
        System.out.println("这是主线程,期望在t线程结束后打印");
    }
}

这里的语法是这样,在main线程中写入t.join(),就是让main线程等待t线程执行完毕再继续执行。
在这里插入图片描述
从系统调度的角度来说,这里就是让main线程主动放弃被系统调度的机会,直到 t 线程执行完,再恢复。
明确来讲,这里不是确定的“执行顺序”,而是确定的“结束顺序”。

在这里插入图片描述
join方法也可以设置一个时间,避免要等的线程内部设计出了死循环,而出现的“死等”。较为常用的就是这种join(long millis)

四、线程的状态

之前谈到进程有就绪和阻塞两种状态。使用Jconsole也可以观察到线程的状态
Java中,对线程的状态进行了进一步的细分:

状态对应的含义
NEWThread对象创建好了,但是还没有调用start方法在系统中创建线程。
TERMINATEDThread对象仍然存在,但是系统内部的线程已经执行完了
RUNNABLE就绪状态,表示这个线程正在CPU上执行,或者随时准备去CPU上执行
TIMED_WAITING指定时间的阻塞,到达一定时间后自动解除阻塞。例如:sleep()、join(long millis)
WAITING不带时间的阻塞(死等),必须满足一定的条件才解除阻塞
BLOCKED由于锁竞争,引起的阻塞

在这里插入图片描述
讲到这里,大部分的状态我们都见识过了。对于BLOCKED的状态,涉及到我们所要讲的线程安全问题,后面在详细论述。


http://www.kler.cn/a/407048.html

相关文章:

  • C#中面试的常见问题001
  • Vue前端开发2.3.5 条件渲染指令
  • C语言数据结构-链表
  • HttpServletRequest req和前端的关系,req.getParameter详细解释,req.getParameter和前端的关系
  • 超高流量多级缓存架构设计!
  • 基于Boost库的搜索引擎
  • 【论文笔记】LLaVA-KD: A Framework of Distilling Multimodal Large Language Models
  • 自动化测试用例编写详解
  • 机器学习杂笔记1:类型-数据集-效果评估-sklearn-机器学习算法分类
  • PH热榜 | 2024-11-23
  • RabbitMQ高可用延迟消息惰性队列
  • Unity图形学之法线贴图原理
  • Python设计模式详解之10 —— 外观模式
  • 1123--日期类
  • 华为防火墙技术基本概念学习笔记
  • 医学AI公开课·第一期|Machine LearningTransformers in Med AI
  • D77【 python 接口自动化学习】- python基础之HTTP
  • 对撞双指针(七)三数之和
  • Jetpack Compose 如何布局解析
  • 系统设计---RBAC模型与ABAC模型
  • 【H2O2|全栈】JS进阶知识(九)ES6(5)
  • 大语言模型---Llama模型文件介绍;文件组成
  • 探索Python网络请求新纪元:httpx库的崛起
  • 小白投资理财 - 解读威廉分形指标 Williams Fractals
  • 高度统一:极大和极小如何统于一
  • 0基础如何进入IT行业?