Java高级特性 - 多线程基础(2)常用函数【第1关:线程的状态与调度 第2关:常用函数(一)第3关:常用函数(二)】

news2024/11/25 23:27:30


目录

第1关:线程的状态与调度 

第2关:常用函数(一)

第3关:常用函数(二)

第1关:线程的状态与调度 

相关知识

为了完成本关你需要掌握:

1.线程的状态与调度;

2.线程执行的优先级。

线程的状态与调度

如果看懂下图,你对线程的了解就会更上一层楼。

  1. 当我们使用new关键字新建一个线程,这个时候线程就进入了新建状态(New),也就是图中未启动状态;

  2. 调用start方法启动线程,这个时候就进入了可运行状态,也就是就绪状态(Runnable);

  3. 就绪状态获取了CPU资源,开始执行run方法,就进入了运行状态(Running);

  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种;

  • 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁);
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中;
  • 其他阻塞:运行的线程执行sleep()join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁);
  1. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程优先级

我们知道,多个线程的执行是随机的,在一个时段执行哪个线程是看哪一个线程在此时拥有CPU的执行权限。

Java中线程有优先级,优先级高的线程会获得较多的运行机会。

Java线程的优先级用整数表示,取值范围是1~10Thread类有以下三个静态常量:

 
 
  1. static int MAX_PRIORITY
  2. 线程可以具有的最高优先级,取值为10。
  3. static int MIN_PRIORITY
  4. 线程可以具有的最低优先级,取值为1。
  5. static int NORM_PRIORITY
  6. 分配给线程的默认优先级,取值为5。

如果要设置和获取线程的优先级,可以使用Thread类的setPriority()getPriority()方法。

每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY

线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。

JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。

关于线程调度与优先级你还需要了解:

  1. 线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
  2. 线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
  3. 线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
  4. 线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
  5. 线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。

 

 

 

第2关:常用函数(一)

相关知识

本关你需要掌握sleepjoin函数的用法。

sleep()函数

sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。

使用方式很简单在线程的内部使用Thread.sleep(millis)即可。

sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用,目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;

sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

join()函数

join函数的定义是指:等待线程终止。

我们在运行线程的时候可能会遇到,在主线程中运行子线程,主线程需要获取子线程最终执行结果的情况。

但是有很多时候子线程进行了很多耗时的操作,主线程往往先于子线程结束,这个时候主线程就获取不到子线程的最终执行结果了。

使用join函数,可以解决这一问题。

我们通过两个示例来理解:

不使用join函数的版本:

class MyThread extends Thread {

private String name;

public MyThread(String name) {
this.name = name;
}

public void run() {
System.out.println("子线程开始运行");

for (int i = 0; i < 5; i++) {
System.out.println("子线程" + name + "运行" + i);
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackT\frace();
}
}
System.out.println("子线程结束");
}
}

public class Test {
public static void main(String[] args) {
Thread t = new MyThread("子线程A");
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行" + i);
}

System.out.println("主线程结束");
}
}

输出结果(每次都不一样):

主线程执行0 主线程执行1 主线程执行2 主线程执行3 主线程执行4 主线程结束 子线程开始运行 子线程子线程A运行0 子线程子线程A运行1 子线程子线程A运行2 子线程子线程A运行3 子线程子线程A运行4 子线程结束

可以发现每次运行,主线程都是先于子线程结束的。

使用join函数:

package step1;

class MyThread extends Thread {

private String name;

public MyThread(String name) {
this.name = name;
}

public void run() {
System.out.println("子线程开始运行");

for (int i = 0; i < 5; i++) {
System.out.println("子线程" + name + "运行" + i);
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackT\frace();
}
}
System.out.println("子线程结束");
}
}

public class Test {
public static void main(String[] args) {
Thread t = new MyThread("子线程A");
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行" + i);
}
try {
t.join();
} catch (Exception e) {
e.printStackT\frace();
}
System.out.println("主线程结束");
}
}

输出结果:

主线程执行0 主线程执行1 主线程执行2 主线程执行3 子线程开始运行 主线程执行4 子线程子线程A运行0 子线程子线程A运行1 子线程子线程A运行2 子线程子线程A运行3 子线程子线程A运行4 子线程结束 主线程结束

可以发现无论运行多少次,主线程都会等待子线程结束之后在结束。

编程要求

请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:

  • 创建自定义线程,实现求第num项斐波那契数列的值num0开始,并且在main函数中获取子线程最终计算的结果。

测试说明

补充完代码后,点击测评,平台会对你编写的代码进行测试,当你的结果与预期输出一致时,即为通过。

输入:5 输出:子线程计算结果为:5

输入:8 输出:子线程计算结果为:21

输入:10 输出:子线程计算结果为:55

/*获取子线程执行的结果并输出*/
package step2;

import java.util.Scanner;

public class Task {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int num = sc.nextInt();
		//请在此添加实现代码
        /********** Begin **********/
        Thread t=new MyThread("子线程",num);
        t.start();
    /********** End **********/	
	}
}
		//请在此添加实现代码
   /********** Begin **********/
class MyThread  extends Thread  {
    private int num;// 需要计算的斐波那契数列的项数
    private String name;  // 线程名称
    public MyThread(String name,int num){
        this.num=num;
        this.name=name;
    }
        
    public void run(){
        int[] arr = new int[2];
        arr[0] = 1;
        arr[1] = 1;
        for(int i=2; i<num; i++){
            int tmp =arr[1];
            arr[1]=arr[0]+arr[1];
            arr[0]=tmp;
        }
        // 输出结果
        System.out.println("子线程计算结果为:" + arr[1]);

    }
}
   /********** End **********/

第3关:常用函数(二)

相关知识

为了完成本关任务你需要掌握:

1.yield函数的用法;

2.wait函数的用法;

3.其他常用函数的使用。

yield 函数

yield函数可以理解为“让步”,它的作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

示例:

public class Task {

public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
MyThread mTh1=new MyThread("A");
MyThread mTh2=new MyThread("B");
mTh1.start();
mTh2.start();
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}

}
class MyThread extends Thread {
private String name;

public MyThread(String name) {
this.name = name;
}

public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(name + "线程" + "执行" + i);
if(i == 10){
this.yield();
}
}
}
}

运行结果有两种情况:

第一种情况:A(线程)当执行到10时会让掉CPU时间,这时B(线程)抢到CPU时间并执行。

第二种情况:B(线程)当执行到10时会让掉CPU时间,这时A(线程)抢到CPU时间并执行。

我们还可以考虑在其中加入setPriority函数改变线程优先级从而改变线程的执行顺序。

wait 函数

要弄明白wait函数我们首先需要了解线程锁的概念。 线程锁:其实就像我们日常生活中的锁,如果一个房子上了锁,别人就进不去,在Java中也类似,如果一段代码取得了锁,那么其他地方就不能运行这段代码,只能等待锁的释放。

这里我们会涉及到两个函数,1.wait(),2.notify()。这两个函数都是Object类自带的函数。

在下面的例子中我们会使用synchronized(Obj)来实现线程同步,同步的概念后面会讲到,这里不理解没关系,不影响对于wait函数的理解。

从功能上来说:

  • wait()就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行;

  • 相应的notify()就是对对象锁的唤醒操作。

但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。

Thread.sleep()Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

纸上得来终觉浅,欲知此事须躬行,我们还是通过实例来学习这些函数的用法。

问题:建立两个线程,A线程打印5A,B线程打印5B,要求线程同时运行,交替打印5AB

这个问题用Objectwait()notify()就可以很方便的解决。代码如下:

public class MyThread implements Runnable {

private String name;
private Object prev;
private Object self;
private MyThread(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
public void run() {
int count = 5;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;

self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackT\frace();
}
}

}
System.exit(0);//退出jvm
}

public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
MyThread ta = new MyThread("A", b, a);
MyThread tb = new MyThread("B", a, b);


new Thread(ta).start();
Thread.sleep(100); //确保按顺序A、B执行
new Thread(tb).start();
Thread.sleep(100);
}
}

运行程序,结果为:

ABABABABAB

线程常用函数总结

下表列出的是线程类常用的函数:

函数描述
public void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
public void run()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
public final void setName(String name)改变线程名称,使之与参数 name 相同。
public final void setPriority(int priority)更改线程的优先级。
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。
public final void join(long millisec)等待该线程终止的时间最长为 millis 毫秒
public void interrupt()中断线程。
public final boolean isAlive()测试线程是否处于活动状态。
public static void yield()暂停当前正在执行的线程对象,并执行其他线程。
public static void sleep(long millisec)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
public static boolean holdsLock(Object x)当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
public static Thread currentThread()返回对当前正在执行的线程对象的引用。
public static void dumpStack()将当前线程的堆栈跟踪打印至标准错误流。

编程要求

请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:

  • 建立三个线程,A线程打印5E,B线程打印5DC线程打印5U,要求线程同时运行,交替打印5EDU
/**
使用三个线程交替打印5次EDU。
*/
package step3;

public class MyThread implements Runnable {   /********** Begin **********/
    private String name;
    private Object prev;
    private Object self;
    //构造函数,初始化线程名字、前一锁对象和自己的锁对象
    private MyThread(String name,Object prev,Object self){
	this.name = name;
	this.prev = prev;
	this.self = self;
}
//实现Runnable接口的run方法
public void run(){
	//先设定循环次数
	int count = 5;
	//循环输出线程名字
	while(count>0){
		//获取前一锁对象的监视器锁
		synchronized(prev){
			//获取自己锁对象的监视器锁
			synchronized(self){
				//输出线程名字
				System.out.print(name);
				//修改循环次数
				count--;
				//唤醒在当前锁对象上等待的线程
				self.notify();
			}
			try {
				//释放前一锁对象的监视器锁,使线程进入等待状态
                prev.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
			}
		}
	}
	//退出程序
	System.exit(0);
}
//主方法
 public static void main(String[] args) throws Exception {  
     //创建三个锁对象
    Object a = new Object();
    Object b = new Object();
    Object c = new Object();
	//创建三个线程对象,分别传入线程名字和前一个锁对象、自己的锁对象
	MyThread ta = new MyThread("E",c,a);
	MyThread tb = new MyThread("D",a,b);
	MyThread tc = new MyThread("U",b,c);
	//创建三个线程并启动
	new Thread(ta).start();
	Thread.sleep(100);
	new Thread(tb).start();
	Thread.sleep(100);
	new Thread(tc).start();
	Thread.sleep(100);
}      
/********** End **********/  
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/429441.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux内核中常用的数据结构和算法

文章目录链表红黑树无锁环形缓冲区Linux内核代码中广泛使用了数据结构和算法&#xff0c;其中最常用的两个是链表和红黑树。 链表 Linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种数据结构。链表所包含的元素可以动态创建并插入和…

APP自动化测试(14)-利用xpath定位元素

一、元素定位的困难 定位元素时有时无法准确定位到我们想要的元素&#xff0c;存在如下几种情况 1、通过一个条件无法准确定位到元素&#xff0c;需要进行条件组合 2、某元素无法唯一定位到&#xff0c;但是同级的其他元素可以唯一定位 3、某元素的属性无论如何组合都无法唯…

训练机器学习模型,可使用 Sklearn 提供的 16 个数据集 【下篇】

数据是机器学习算法的动力&#xff0c;scikit-learn或sklearn提供了高质量的数据集&#xff0c;被研究人员、从业人员和爱好者广泛使用。Scikit-learn&#xff08;sklearn&#xff09;是一个建立在SciPy之上的机器学习的Python模块。它的独特之处在于其拥有大量的算法、十分易用…

AOP使用场景记录总结(缓慢补充更新中)

测试项目结构: 目前是测试两个日志记录和 代码的性能测试 后面如果有其他的应用场景了在添加.其实一中就包括了二,但是没事,多练一遍 1. 日志记录 比如说对service层中的所有增加,删除,修改方法添加日志, 记录内容包括操作的时间 操作的方法, 方法的参数, 方法所在的类, 方法…

CSS :autofill 如何覆盖浏览器自动填充表单的样式

CSS :autofill 如何覆盖浏览器自动填充表单的样式 :autofill 伪类匹配浏览器自动填充值的 input 元素. 如果用户继续编辑这个元素内容就会停止匹配. #name:autofill {background-color: red !important;border: 6px solid red; } #name:-webkit-autofill {background-color: …

OpenAI-ChatGPT最新官方接口《审核机制》全网最详细中英文实用指南和教程,助你零基础快速轻松掌握全新技术(七)(附源码)

Moderation 审核机制前言Introduction 导言Quickstart 快速开始其它资料下载ChatGPT 作为一个大型人工智能语言模型&#xff0c;在提供用户便捷交流的同时也承担着内容审核的责任。为了保护用户和社会免受不良信息的影响&#xff0c;ChatGPT 特别注重关于内容的审核。当用户发送…

UDS统一诊断服务【五】诊断仪在线0X3E服务

文章目录前言一、诊断仪在线服务介绍二、数据格式2.1&#xff0c;请求报文2.2&#xff0c;子功能2.3&#xff0c;响应报文前言 本文介绍UDS统一诊断服务的0X3E服务&#xff0c;希望能对你有所帮助 一、诊断仪在线服务介绍 诊断仪在线服务比较简单&#xff0c;其功能就是告诉服…

winForm目录文件介绍

先看项目结构 引用&#xff1a;添加引用&#xff0c;选择自己需要的程序集添加 app.config:配置文件 form1.cs&#xff1a;窗体文件&#xff0c;创建一个窗体所要具备的文件 program&#xff1a;程序入口点 再看创建项目后各个文件夹的含义 .sln:解决方案文件&#xff0c;…

网络模型-网络体系结构(OSI、TCP/IP)

网络模型&#xff08;网络体系结构&#xff09;网络模型网络的体系结构OSI模型TCP/IP模型OSI和TCP/IP模型对应关系图常见网络协议网络模型 网络的体系结构 1、网络采用分而治之的方法设计&#xff0c;将网络的功能划分为不同的模块&#xff0c;以分层的形式有机组合在一起。 …

智慧果园系统——以水肥一体化系统功能为基础实现智慧果园系统项目 需求文档

文章目录一、引言1.文档的作用2.文档的标准3.产品的范围二、综合描述1.项目前景2.项目目标3.项目功能4.调研和面谈A.硬数据采样a)硬数据分析的形式b)定量硬数据c)定性硬数据B.面谈a)第一次面谈&#xff1a;开放式问题b)第二次面谈&#xff1a;封闭式问题&#xff0b;开放性问题…

NIFI大数据进阶_离线同步MySql数据到HDFS_01_实际操作---大数据之Nifi工作笔记0029

然后我们实际操作一下如何把mysql中的数据同步到hdfs中去,这里注意,这里是查询mysql中的表中的数据,然后放到 hdfs中去,并不是说,如果mysql数据表中的数据变化了,就自动同步到hdfs,这个功能后面我们再说,这是增量同步 用到的是其他的处理器 首先我们创建一个处理器组mysqlto…

【行为型模式】迭代器模式

文章目录1、简介2、结构3、实现方式3.1、案例引入3.2、结构分析3.3、具体实现4、迭代器模式优缺点5、应用场景1、简介 迭代器模式(Iterator)是一种行为设计模式&#xff0c;它允许我们遍历一个复杂的集合对象而无需暴露其内部表示。它提供了一种统一的方式来访问一个聚合对象中…

在docker上安装MySQL和Redis

1. 通过docker命令下载mysql5.7镜像mysql5.7前期准备2. 通过docker命令下载mysql8.0镜像mysql8.0前期准备 3. 通过docker命令下载redis镜像redis前期准备 本文永久更新地址: 1. 通过docker命令下载mysql5.7镜像 mysql5.7前期准备 在Linux虚拟机上创建一个文件夹用来持久化数据…

replugin原理笔记

Replugin源码目录主要有4个工程组成&#xff0c;其组成如下图所示&#xff0c;包括2个gradle工程&#xff0c;2个Android library工程。 replugin-host-gradle replugin-host-library replugin-plugin-gradle replugin-plugin-library Replugin是一套完整的、稳定的、适合全面…

C++ 基础回顾(下)

C 基础回顾&#xff08;下&#xff09; 目录C 基础回顾&#xff08;下&#xff09;前言模板和泛型编程动态内存与数据结构动态内存数据结构继承与多态继承多态简单的输入输出工具与技术命名空间异常处理多重继承与虚继承时间和日期前言 C之前学过一点&#xff0c;但是很长时间…

进销存管理系统是什么?进销存管理系统优点?

库存管理不当导致物资浪费/过期/损坏&#xff0c;增加企业成本和风险&#xff1b; 无法有效监控销售和采购流程&#xff0c;交易的准确性和时效性不到位&#xff1b; 财务管理混乱&#xff1b; ...... 你是否遇到过以上问题&#xff1f; 进销存管理系统&#xff08;Inventory …

Softing FG-200——将FF H1现场总线集成到工业以太网

基金会现场总线FF&#xff08;FOUNDATION Fieldbus&#xff09;是专为过程自动化设计的通信协议&#xff0c;包含低速总线H1&#xff08;31.25kbits/s&#xff09;标准和高速以太网HSE&#xff08;High Speed Ethernet&#xff0c;100Mbits/s&#xff09;标准。FF H1主要针对于…

200左右蓝牙耳机有哪些推荐?质量好的平价蓝牙耳机分享

现在蓝牙耳机基本上都是人手必备的存在了&#xff0c;对比上千元的蓝牙耳机&#xff0c;两百左右价位蓝牙耳机才是更多人的优先选择、废话不多说&#xff0c;下面我就来为大家推荐几款200元上下&#xff0c;质量和口碑都好的蓝牙耳机&#xff0c;准备入手蓝牙耳机的小伙伴可以作…

Mac配置QT

Mac配置QT 前言&#xff1a; 系统版本&#xff1a;Ventura 13.2.1 (22D68) 先安装homebrew&#xff0c;参考&#xff1a; https://blog.csdn.net/ZCC361571217/article/details/127333754 Mac配置&#xff1a; 安装Qt与Qt Creator&#xff1a; 通过Homebrew安装(若没Homeb…

VL817S与之前其他型号的区别与改动

相对于VL817C0以及VL817B0来说&#xff0c;VL817S使用外部供电不需要接入5V&#xff0c;HUB 5V 请参考参考设计接地。内部3.3 LDO输出请悬空。1。2V LX和FB请悬空。如下所示&#xff0c;详见参考设计。 1、3.3V和1.2V之间的时序要求是怎么样的&#xff1f; 下图是VL817(S) 上电…