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

网站建设分享文章晚上免费b站软件

网站建设分享文章,晚上免费b站软件,网站上传在空间哪里去了,外贸网站建设和网站推广要怎么做文章目录 前言与回顾创建一个多线程线程的创建以及运行机制简述step1: 继承Thread类step2: 实现Runable接口step3: 基于step1使用匿名内部类step4: 基于step2使用匿名内部类step5: 基于step4使用lambda表达式(推荐) Thread的常见方法关于jconsole监视线程的工具构造方法解析获取…

文章目录

  • 前言与回顾
  • 创建一个多线程
    • 线程的创建以及运行机制简述
    • step1: 继承Thread类
    • step2: 实现Runable接口
    • step3: 基于step1使用匿名内部类
    • step4: 基于step2使用匿名内部类
    • step5: 基于step4使用lambda表达式(推荐)
  • Thread的常见方法
    • 关于jconsole监视线程的工具
    • 构造方法解析
    • 获取 ID / 名称
    • 查看 / 设置后台线程
    • 查看线程的状态
    • 获取 / 设置线程的优先级
    • 查看线程存活
  • 中断线程以及阻塞唤醒机制
    • 使用自定义标志位终止线程
      • lambda中的变量捕获
    • 使用interrupt
      • interrupt方法对于阻塞的打断
  • 等待一个线程(join方法)
    • join方法的基本说明
    • join方法的使用及线程状态观察
  • 线程休眠机制
    • 如何获取线程的引用
    • sleep方法
    • sleep(0) 和 yield()

前言与回顾

基础的一些关于线程/进程的一些基础的概念, 已经在之前的帖子中有过解释
简单复习一下

  • 线程是轻量级的进程(进程太重了, 创建销毁代价大)
  • 进程包括线程
    进程是操作系统资源分配的基本单位
    线程是操作系统调度执行的基本单位
  • 线程的调度是操作系统进行的, 在应用层无法进行干预, 也无法感知
  • 线程是操作系统级别的概念, 操作系统内核实现了这种机制并封装提供用户一些API
    Java中的Thread类可以视为是对操作系统的API的进一步封装和抽象

创建一个多线程

线程的创建以及运行机制简述

我们创建线程的最基本的方法其实是两种(后续还有)

  • 通过定义一个类继承Thread类重写run方法
  • 通过定义一个类实现Runnable接口重写run方法

run方法相当于主线程的main, 作为每一个线程的程序运行的入口, 我们通过start方法启动一个线程, 这时候操作系统的内部会真正的创建一个线程(源码层面是去调用的start0native修饰的本地方法, 自行调用cpp动态链接库), start0会调用run方法, 所以这相当于是一种回调函数的机制
但是我们提供了五种创建线程的方式, 其实本质都是基于上述两种(后续还要其他方法)
关于start的源码

public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionality added* to this method in the future may have to also be added to the VM.** A zero status value corresponds to state "NEW".*/if (threadStatus != 0)throw new IllegalThreadStateException();/* Notify the group that this thread is about to be started* so that it can be added to the group's list of threads* and the group's unstarted count can be decremented. */group.add(this);boolean started = false;try {start0();started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}}// 本地方法private native void start0();

也就是真正起作用的是start0, 如果好奇native修饰的方法的具体实现, 可以去JDK官网上自行查看JVM源码

step1: 继承Thread类

实现代码如下, 我们让每一个线程都睡眠1000ms便于观察

/*** 第一种创建线程的方式* 继承Thread类重写run方法*/class MyThread extends Thread{@Overridepublic void run() {while(true){System.out.println("hello thread!");// 睡眠一秒钟try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class ThreadTest {public static void main(String[] args) {Thread t = new MyThread();// 开启一个线程t.start();// 主线程也执行while(true){System.out.println("hello main!");// 睡眠一秒钟try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

开始运行, 可以明显的看到线程t和线程main在交替的执行, 这也说明了是我们的底层的抢占式调度模型在起作用, 多个线程不断地抢夺cpu时间片

在这里插入图片描述

step2: 实现Runable接口

每一个逻辑加上一个sleep方法休眠便于观察

/*** 第二种创建线程的方式* 定义一个类实现Runnable接口*/
class MyRunnable implements Runnable{@Overridepublic void run() {while(true){System.out.println("hello thread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadTest {public static void main(String[] args) throws InterruptedException {// 创建一个线程tThread t = new Thread(new MyRunnable());// 开启一个线程tt.start();// 主线程的逻辑while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

在这里插入图片描述

step3: 基于step1使用匿名内部类

/*** 基于第一种方法使用匿名内部类*/
public class ThreadTest {public static void main(String[] args) throws InterruptedException {// 创建t线程Thread t = new Thread() {@Overridepublic void run() {while (true) {System.out.println("hello thread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};// 开启t线程t.start();// 主线程的逻辑while (true) {System.out.println("hello main!");Thread.sleep(1000);}}
}

step4: 基于step2使用匿名内部类

public class ThreadTest {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(new Runnable() {@Overridepublic void run() {while(true){System.out.println("hello thread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});// 开启线程 tt.start();// 主线程的逻辑如下while(true){System.out.println("hello main!");Thread.sleep(1000);}}
}

step5: 基于step4使用lambda表达式(推荐)

lambda表达式本质上就是一个匿名的函数, 最主要的用途就是作为回调函数, 很多语言都有, 只不过叫法有差异而已

  • 底层编译器会对lambda表达式进行处理, 创建了一个匿名的函数式接口的子类, 并且创建了对应的实例并且重写里面的方法

代码实例

/*** 使用lambda表达式进行问题的描述*/
public class ThreadTest {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hello t!!!");}}, "t");// 开启t线程t.start();// 主线程中的逻辑while(true){Thread.sleep(1000);System.out.println("hello main!!!");}}
}

在这里插入图片描述
这里可以看到, 我们的两个线程正在交替的执行逻辑
我们推荐这种方式进行平时的线程的测试, 因为比较的轻量级

Thread的常见方法

关于jconsole监视线程的工具

构造方法解析

是我们的JDK中自带的一个检测当前线程的工具, 在我们的JDK安装路径中的bin目录下, 可以在这个目录下找到这个检测的工具
在这里插入图片描述
我们开启一个多线程的逻辑, 我们这里的测试代码是上面的step5创建线程的方法, 可以先去看一下…
打开之后是下面的窗口
在这里插入图片描述
这里我们查看 t线程和main线程

在这里插入图片描述
在这里插入图片描述
这里我们可以看到线程的状态
比如现在的两个线程都位于TIMED_WAITING状态(sleep(1000))
还可以查看当前线程的堆栈跟踪状态是什么


这里其实还说明了一些问题, 我们的后台其实存在多个线程(本质上是后台线程, 比如跟网络通讯相关的线程, 还有跟垃圾回收 GC 相关的一些线程) 这些线程维持我们 Java 程序的正常执行


在这里插入图片描述
这张图是JDK的帮助文档的截图, 我们逐一解释一下里面的一些主要的方法(关于线程组创建线程的方法我们暂时略过, 后期回来继续说, 其实就是把线程进行分组)

方法签名描述
Thread()直接创建线程对象
Thread(Runnable target)使用实现了Runnable接口的对象来创建线程
Thread(String name)创建线程并传入一个名字(便于使用jconsole进行监视)
Thread(Runnable target, String name)前两种的结合
Thread(ThreadGroup group, Runnable target)使用线程组来创建线程(现阶段了解)

获取 ID / 名称

对于每一个线程来说, 都有一个独一无二的 ID (标识码, 类似PID)
对于每个线程来说, 程序开发者可以选择指定一个名字(或者系统自动分配)

  • getID: 获取当前线程的标识码
  • getName: 获取当前线程的名称
public class ThreadTest {public static void main(String[] args) throws InterruptedException{// 创建一个t线程Thread t = new Thread(() -> {while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName());}}, "t线程");// 开启t线程t.start();// 主线程中的逻辑while(true){Thread.sleep(1000);System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName());}}
}

在这里插入图片描述
可以观测到此时的随机调度的线程的 ID 以及 name

查看 / 设置后台线程

后台线程其实就是守护线程, 其实就是进程的"幕后人员"

  • setDaemon(boolean) : 传入一个true把当前线程设置为守护线程(start前)
  • isDaemon(): 查看当前线程是否是守护线程

守护线程是当所有的用户线程全部结束之后, 自动就结束了, 但是如果守护线程自己选择结束了, 那也算是结束了, 所以我们的守护线程一般用于链接网络, 维护数据库日志等等场景

class ThreadTest01 {public static void main(String[] args) throws InterruptedException {// 创建一个t线程Thread t = new Thread(() -> {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("守护线程执行ing");}}, "t线程");// 设置t线程为守护线程t.setDaemon(true);// 开启t线程t.start();while (true) {Thread.sleep(1000);System.out.println("main线程执行ing");}}
}

在这里插入图片描述
通过jconsole检测也可以发现两个线程的情况, 可以看到两个位置都是TIMED-WAITING状态

查看线程的状态

  • getState(): 查看当前线程的状态(有六种状态)
class ThreadTest01 {public static void main(String[] args) throws InterruptedException {// 创建一个t线程Thread t = new Thread(() -> {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("守护线程执行ing " + Thread.currentThread().getState());}}, "t线程");// 设置t线程为守护线程t.setDaemon(true);// 开启t线程t.start();while (true) {Thread.sleep(1000);System.out.println("main线程执行ing " + Thread.currentThread().getState());}}
}

在这里插入图片描述
实质上有多种状态描述, 我们此时的状态是可运行状态

获取 / 设置线程的优先级

class ThreadTest01 {public static void main(String[] args) throws InterruptedException {// 创建一个t线程Thread t = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 查看当前线程的优先级System.out.println("默认的优先级: " + Thread.currentThread().getPriority());}, "t线程");t.start();System.out.println("最低优先级: " + Thread.MIN_PRIORITY + " 最高优先级: " + Thread.MAX_PRIORITY);}
}

在这里插入图片描述

  • setPriority(): 设置线程的优先级(0 - 10)

这其实是一个概率问题, 定义一个线程抢占cpu时间片的概率, 但不是绝对的

查看线程存活

  • isAlive(): 判断当前线程是否存活

请注意, 线程的存活和线程对象的生命周期一般是不一样的
比如下面的代码

class ThreadTest02{public static void main(String[] args) throws InterruptedException {// 创建一个t线程Thread t = new Thread(() -> {System.out.println("...");}, "t线程");t.start();while(true){Thread.sleep(1000);System.out.println(t.isAlive());}}
}

在这里插入图片描述
此时线程以及结束了(run方法执行结束), 但是线程对象并没有销毁

中断线程以及阻塞唤醒机制

使用自定义标志位终止线程

定义一个static变量作为标志位然后打标记


public class ThreadTest {// 自定义一个标志位打标记private static boolean isFinished = false;public static void main(String[] args) throws InterruptedException {// 使用lambda机制创建一个线程Thread t = new Thread(() -> {while(!isFinished){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName());}}, "t线程");// 开启t线程t.start();// 三秒之后终止t线程Thread.sleep(3000);isFinished = true;}
}

在这里插入图片描述
可以看到 3s 之后终止 t 线程

lambda中的变量捕获

如果上面的代码的标志位是局部变量, 那么我们就触发了lambda表达式中的变量捕获语法
在这里插入图片描述
这个代码我们发现报错了

  • 原因: 这其实就是lambda语法中的变量捕获机制, 对于lambda表达式来说, 本质是一种回调函数, 执行时机很可能是很久之后了, 此时main函数存在与否都不好说, 假设main执行结束了, 此时isFinished变量其实也就销毁了, 那lambda中访问的isFinished变量又是什么呢 ?

  • 解决方案: 为了解决上述的问题, Java中采取的做法是, 在定义lambda的时候, 直接就把变量拷贝一份给lambda中的变量, 所以其实上述代码中, lambda中的isFinished变量跟外部的isFinished的变量, 本质上是两个变量, 这样处理的好处就是, 此时外部的情况不管怎么变, 即使变量销毁了, 对lambda表达式内部也不会造成影响

  • 不允许修改: 根据上面的解释, 如果一边改变, 另一边不变, 这样处理就会对程序员造成更对的困扰, 所以Java这边的做法是, 压根不允许我们对变量的值进行修改, 对于引用变量来说, 不允许指向修改, 但是内部的值是可以修改的…(C++对于这里的操作相对更复杂, 需要你自己定义变量的生命周期以及是否进行拷贝)

  • 和定义外部变量的区别: 那为什么上面的定义一个外部变量就可以修改呢, 因为所属的语法机制不一样, 上面所属的语法体系是内部类访问外部类变量, 当然可以修改

引用变量的场景

在这里插入图片描述

在这里插入图片描述
此时可以修改内部的值, 但是不可以修改引用

使用interrupt

关于终止线程, 我们上面给出了一种修改终止标志位的方案, 但其实Java中的Thread类天然的提供了一个标志位用来进行变量的修改


我们分析一下下面的代码

public class ThreadTest {public static void main(String[] args) throws InterruptedException {// 关于使用内部自带的标志位修改Thread t = new Thread(() -> {while(!Thread.currentThread().isInterrupted()){System.out.println("hello main!!!");;//Thread.sleep(1000);}});// 开启线程tt.start();// 三秒后终止线程tThread.sleep(3000);t.interrupt();}
}

上述代码是可以正常结束线程的, 因为3s之后, 我们把t线程的标志位设置为true, 然后跳出循环…
在这里插入图片描述
在这里插入图片描述
观察一下源码就可以发现, 其实跟我们自行定义标志位的做法是一致的

interrupt方法对于阻塞的打断

我们尝试分析一下下面的代码

public class ThreadTest {public static void main(String[] args) throws InterruptedException {// 关于使用内部自带的标志位修改Thread t = new Thread(() -> {while(!Thread.currentThread().isInterrupted()){System.out.println("hello main!!!");;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});// 开启线程tt.start();// 三秒后终止线程tThread.sleep(3000);t.interrupt();}
}

我们运行的结果如下
在这里插入图片描述
我们尝试打断线程, 但是失败了, 只是在控制台打印了堆栈信息, 但是线程并没有被打断…
这里我们没办法查看sleep方法的源码(native修饰的本地方法)

  • 原因: 其实就是我们在调用interrupt方法的同时, 会唤醒sleep等等类似阻塞效果的方法, 并抛出异常InterruptedException后被唤醒(其实就是唤醒的机制), 并且把标志位interrupted重新设置为false, 所以下次循环还可以进入…
  • 处理逻辑: 那为什么要这样处理呢, 是不是毫无用处多此一举呢… 我认为, 这样处理的逻辑还是相对比较合理的, 因为留给了程序员更多的操作空间, 在被唤醒之后, 程序员可以选择下面三种处理方式
    1 . 直接跳出执行逻辑(比如通过break)
    2 . 执行操作逻辑后再跳出(先执行一段逻辑代码处理, 然后break)
    3 . 忽视这个信号, 继续执行

在这里插入图片描述
上面是我们对于这个终止信号的三种处理态度

等待一个线程(join方法)

join方法的基本说明


由于多个线程之间都是随机调度执行的, 并发执行, 随机调度
所以站在程序员的角度, 我们不喜欢随机性, join方法可以要求多个线程之间的结束的先后顺序…
虽然可以通过sleep来设置等待的时间, 但是这种机制往往是不科学的, 比如我们现在有一个需求, 想要t线程执行结束后, main线程立即开始执行, 这时候使用sleep的合理性就很差了.


在这里插入图片描述
我们这里有三个常见的方法

  • join(): 无时间期限的等待
  • join(long millis): 设置最长的等待时间为millis, 单位为毫秒
  • join(long millis, int nanos): 设置最长的等待期限是毫秒+纳秒(不常用)
    对于第三个方法的说明, 其实做不到那么精确, 除非使用实时操作系统…

第一个进入WAITING状态, 第二三个进入TIMED_WAITING状态

join方法的使用及线程状态观察


public class ThreadTest {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for(int i = 0; i < 5; i++){System.out.println("hello t ! ! !");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}, "t");// 启动t线程t.start();// t执行完毕再执行main线程t.join();System.out.println("hello main ! ! !");}
}

在这里插入图片描述
在t线程执行5s之后, main线程继续执行, 我们使用jconsole工具查看一下情况(修改上面t线程循环次数为50)

在这里插入图片描述
此时t线程处于TIMED_WAITING状态, 因为这个线程的大部分时间都处于sleep(1000)这里…

在这里插入图片描述
观察到main线程处于WAITING状态, 此时正在因为join()产生阻塞(无时间期限)

线程休眠机制

如何获取线程的引用

  • Thread.currentThread(): 静态方法, 返回一个当前线程对象的引用(相当于this关键字)

sleep方法

这个方法没什么可说的, 但是也有需要注意的一点
比如

Thread.sleep(1000);

这个真的是让当前线程休眠 1000ms 吗 ?
其实并没有那么精确, 实际上一般是要略大于 1000ms , 因为sleep(1000), 指的是1000ms之后
该线程再次拥有了抢夺 cpu 时间片的能力, 但是具体需要多长时间能够抢到, 这个其实是一个未知数, 所以一般是要比 1000ms 稍微大一些的…

sleep(0) 和 yield()

在这里插入图片描述

  • yield: 是一个静态方法, 使得当前调用该线程的方法立刻放弃抢夺到的cpu时间片进入就绪状态
  • sleep(0): 和上面yield类似, 都是立刻放弃抢夺到的cpu时间片, 进入抢夺cpu时间片的就绪状态
http://www.rdtb.cn/news/17268.html

相关文章:

  • 新余服装网站建设东莞建设企业网站
  • 网站开发 语音交换链接的其它叫法是
  • wordpress 评论群发seo和sem分别是什么
  • 上海金融网站制作公河南省疫情最新情况
  • 定制网站公司哪家好谷歌自然排名优化
  • 起名网站怎么做最好的网络推广方式
  • 网站开发好还要空间吗苏州网站建设优化
  • 个人网站下载百度搜索量最大的关键词
  • wordpress作者最新评论学seo哪个培训好
  • 做视频网站用什么服务器配置品牌宣传活动策划方案
  • 老河口城乡建设局网站最强大的搜索引擎
  • 网站建设网站定制开发软文推广发稿
  • 专门做外挂的网站公司运营策划营销
  • 短视频seo排名系统百度关键词优化软件网站
  • ps做网站首页2023年广州疫情最新消息
  • 产品设计排版网站谷歌关键词搜索排名
  • 做网站工资多少论坛seo招聘
  • 网站备案号如何查询密码免费发布产品信息的网站
  • wordpress主题兼容济南seo网站关键词排名
  • 微信小程序做链接网站做一个微信小程序需要多少钱
  • 企业做网站有用么谷歌独立站
  • 哪个网站做视频有钱挣seo怎么推广
  • 软件网站是怎么做的吗南京网络营销服务
  • 种子汤唯梁朝伟做视频网站百度查询网
  • 网站开发兼职优化关键词的方法
  • 网站的收费窗口怎么做百度的电话人工客服电话
  • 网站建设所出现的问题网络营销与传统营销的区别
  • 网站上线如何做公司名字谷歌关键词
  • 安徽省住房和建设厅网站qianhu微建站
  • 网站建设金手指排名信誉怎样才能注册自己的网站