线程终止,线程的相关方法,线程状态图以及线程的同步机制。

2023-12-17 06:52

本文主要是介绍线程终止,线程的相关方法,线程状态图以及线程的同步机制。,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

首先我们知道了什么是线程,以及线程的实现方法,接下来我们来了解一下继承Thread类和Runnable接口的区别,其实本质上是没有区别的,因为Thread也实现了Runnable的接口,唯一区别就是使用Runnable接口,可以实现多个线程共享一个资源的情况,而且不会受到单继承的限制,这里我们建议使用Runnable接口。

我们再来聊一下线程终止的操作,线程终止顾名思义就是想让进程停止运行,我们可以通过设置变量的方法来使线程退出,即通知方式,这里我们用一个实例来进行演示:

public class ift {public static void main(String[] args) {Preson2 preson2 = new Preson2(true);Thread thread = new Thread(preson2);thread.start();for (int i = 0; i < 10; i++){try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("hi~");if (i == 5){preson2.setf(false);}}}
}class Preson2 implements Runnable{public Boolean b;public Preson2(Boolean b) {this.b = b;}@Overridepublic void run() {while(b){try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("hello~");}}public void setf(Boolean b){this.b = b;}
}

 

这里我们发现hello只被执行了五次,是因为我们在主线程中设定了终止信息,才实现了该功能;

线程还有一系列的相关方法:

这是线程的常用方法,我们就不一一列举了。

这里提示一下:interrupt方法并不是中断一个线程的运行,而是让正在休眠的线程提前中断休眠,让它重新运作。

这里有俩个非常常用的方法一个是yield:线程的礼让,一个是join:线程插队,线程的礼让:让出cpu,给其他线程先运行,但是会根据cpu的运行状态来确定,如果cpu的资源很丰富则不会礼让成功,所以线程礼让是不确定的,它不一定会成功,线程插队:线程插队跟线程礼让不同,它比较霸道,一旦插队成功是必须先执行完该线程,才会将cpu让出来给其他线程使用。

join方法:

public class ift {public static void main(String[] args) {Thread t3 = new Thread(new T3());//创建子线程for (int i = 1; i <= 10; i++) {System.out.println("hi " + i);if(i == 5) {//说明主线程输出了 5 次 hit3.start();//启动子线程 输出 hello... t3.join();//立即将 t3 子线程,插入到 main 线程,让 t3 先执行try {t3.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}try {Thread.sleep(1000);//输出一次 hi, 让 main 线程也休眠 1s} catch (InterruptedException e) {throw new RuntimeException(e);}}}}class T3 implements Runnable{private int count = 0;@Overridepublic void run() {while (true) {System.out.println("hello " + (++count));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (count == 10) {break;}}}
}

 

yield方法:

public class ift {public static void main(String[] args) {Thread t3 = new Thread(new T3());//创建子线程for (int i = 1; i <= 10; i++) {System.out.println("hi " + i);if(i == 5) {//说明主线程输出了 5 次 hit3.start();//启动子线程 输出 hello... t3.join();//立即将 t3 子线程,插入到 main 线程,让 t3 先执行t3.yield();}try {Thread.sleep(1000);//输出一次 hi, 让 main 线程也休眠 1s} catch (InterruptedException e) {throw new RuntimeException(e);}}}}class T3 implements Runnable{private int count = 0;@Overridepublic void run() {while (true) {System.out.println("hello " + (++count));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (count == 10) {break;}}}
}

 

这里我们发现并没有礼让成功而是交替输出。

接下来我们看一下线程的状态图:
  

Runnable状态在jvm机中又被分为ready和running两个状态。

我们可以通过getState()这个方法来查询线程的当前状态,我们写个实例来看一下:

public class ift {public static void main(String[] args) throws InterruptedException {T t = new T();System.out.println(t.getName() + " 状态 " + t.getState());//NEWt.start();while (Thread.State.TERMINATED != t.getState()) {System.out.println(t.getName() + " 状态 " + t.getState());//TIMED_WAITING,原因线程每运行一次就休眠一秒钟Thread.sleep(500);}System.out.println(t.getName() + " 状态 " + t.getState());//TERMINATED}
}
class T extends Thread {@Overridepublic void run() {while (true) {for (int i = 0; i < 10; i++) {System.out.println("hi " + i + getState());//RUNNABLEtry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}break;}}
}
while (Thread.State.TERMINATED != t.getState()) {System.out.println(t.getName() + " 状态 " + t.getState());//TIMED_WAITING,原因线程每运行一次就休眠一秒钟Thread.sleep(500);
}

该循环内大家肯定有疑问,为什么一直都是TIMED_WAITING状态,为什么刚启动后的RUNNABLE状态打印不了,那是因为运行的过程很快可能就几毫秒,所以根本捕捉不到该状态,但是休眠的时间很长所以打印的都是休眠的状态,运行状态可以在它运行的时候一块打印出来,就像我写的一样,给大家看一下结果:

当我们休眠状态足够短的时候我们发现状态就会发生变化:

public class ift {public static void main(String[] args) throws InterruptedException {T t = new T();System.out.println(t.getName() + " 状态 " + t.getState());//NEWt.start();while (Thread.State.TERMINATED != t.getState()) {System.out.println(t.getName() + " 状态 " + t.getState());//TIMED_WAITING,原因线程每运行一次就休眠一秒钟Thread.sleep(2);}System.out.println(t.getName() + " 状态 " + t.getState());//TERMINATED}
}
class T extends Thread {@Overridepublic void run() {while (true) {for (int i = 0; i < 10; i++) {System.out.println("hi " + i + getState());//RUNNABLEtry {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}break;}}
}

 

接下来我们来学习一下线程的同步机制:什么是线程的同步机制我们先来看一个实例来引出线程同步机制的作用:

public class Tick {public static void main(String[] args) {sellTcik sellTcik = new sellTcik();new Thread(sellTcik).start();new Thread(sellTcik).start();new Thread(sellTcik).start();}}class sellTcik implements Runnable{@SuppressWarnings({"all"})public int tick = 100;Object object = new Object();private Boolean loop = true;public void run(){while(loop){m();}}public void m(){if (tick == 0) {System.out.println("售票结束");loop = false;return;}System.out.println("线程" + Thread.currentThread().getName() + "     " + (--tick));try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

当我们运行代码的时候我们就会发现一些问题:

它的票数可能出现负数的情况,这是为什么呢,这是因为它是三个线程一块进入进行操作的,假如票还有两张,三个线程一块进去检测票数确实大于0,所以会直接拿去卖,买了三张,所以出现 了超票的情况,那我们怎么来解决这样的情况呢,这里就要用到我们的线程同步机制了,它的实现方法是synchronized(),括号内可以添加一个对象,相当于一把锁,只有拿到锁的线程才能进入我们的方法中,但是听起来效率会大大降低,线程的深入学习,以后会进行讨论,我们先来了解目前学习的方法,而且这个锁,有个特点,它对于线程而言必须指向的同一个对象,如果是不同对象,那该锁则是无效的,这里我们使用接口类中的Object类型的对象,因为实现Runnable接口只需要创建一个对象即可,如果我们用的是继承Thread的方法时,我们需要创建多个对象,这是我们可以将Object类型设置为静态属性这里可以给多线程共享相同的资源。所以object对象肯定是相同对象(synchronized()被该方法框起来的代码越少越好,可以提升效率)。

public class Tick {public static void main(String[] args) {sellTcik sellTcik = new sellTcik();new Thread(sellTcik).start();new Thread(sellTcik).start();new Thread(sellTcik).start();}}class sellTcik implements Runnable{@SuppressWarnings({"all"})public int tick = 100;Object object = new Object();private Boolean loop = true;public void run(){while(loop){m();}}public void m(){synchronized (object) {if (tick == 0) {System.out.println("售票结束");loop = false;return;}System.out.println("线程" + Thread.currentThread().getName() + "     " + (--tick));try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

如果设置同步方法

public synchronized static void m1() {
}
它默认的锁就是该类的类名.class

这里我们发现超票的问题得到了很好的解决。(同步机制的互斥锁大多设置为自己的类即类名.class),该锁是必然有效的,因为类只能有一个,可以根据自己的喜好,自行添加锁。

互斥锁:

互斥锁的特征就像上述所说,只允许一个线程拿到锁,等到该线程运行完才能让其他线程进入,而且互斥锁,必须指向的同一对象,如果不同对象进入拿到的锁不同,则无法做到限制的作用,则该锁无效。

线程死锁:

我们直接模拟死锁给大家一个直观的感受:

public class Tick {public static void main(String[] args) {//模拟死锁现象DeadLockDemo A = new DeadLockDemo(true);A.setName("A 线程");DeadLockDemo B = new DeadLockDemo(false);B.setName("B 线程");A.start();B.start();}}class DeadLockDemo extends Thread {static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 staticstatic Object o2 = new Object();boolean flag;public DeadLockDemo(boolean flag) {//构造器this.flag = flag;}@Overridepublic void run() {//1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁//2. 如果线程 A 得不到 o2 对象锁,就会 Blocked//3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁//4. 如果线程 B 得不到 o1 对象锁,就会 Blockedif (flag) {synchronized (o1) {//对象互斥锁, 下面就是同步代码System.out.println(Thread.currentThread().getName() + " 进入 1");synchronized (o2) { // 这里获得 li 对象的监视权System.out.println(Thread.currentThread().getName() + " 进入 2");}}} else {synchronized (o2) {System.out.println(Thread.currentThread().getName() + " 进入 3");synchronized (o1) { // 这里获得 li 对象的监视权System.out.println(Thread.currentThread().getName() + " 进入 4");}}}}
}

 

我们发现A,B两线程都处于了Blocked状态,这就是死锁,互相拿着对方需要的锁,无法继续往下运行。

 

 

 

这篇关于线程终止,线程的相关方法,线程状态图以及线程的同步机制。的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/503483

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti