创建线程的四种方式 线程通信

article/2025/9/24 17:52:53

文章目录

    • 1.1 创建线程
      • 1.1.1 创建线程的四种方式
      • 1.1.2 Thread类与Runnable接口的比较
      • 1.1.3 Callable、Future与FutureTask
    • 1.2 线程组和线程优先级
    • 1.3 Java线程的状态及主要转化方法
    • 1.4 Java线程间的通信
      • 1.4.1 等待/通知机制
      • 1.4.2 信号量
      • 1.4.3 管道

1.1 创建线程

1.1.1 创建线程的四种方式

【1】继承Thread类
【2】实现Runnable接口
【3】实现Callable,获取返回值
【4】实现FutureTask类

Thread类是一个Runnable接口的实现类,Thread类中通过调用私有的init来实现初始化。
在这里插入图片描述
g:线程组
target:实现Runnable接口的线程处理类
name:线程名称,如果没有指定则默认Thread-随机数
stackSize:线程初始栈大小

1.1.2 Thread类与Runnable接口的比较

1:由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
2:Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
3:Runnable接口出现,降低了线程对象和线程任务的耦合性。
4:如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。Thread是扩展了Runnable接口的对象。

1.1.3 Callable、Future与FutureTask

使用Runnable和Thread来创建一个新的线程。但是它们有一个弊端,就是run方法是没有返回值的。而有时候我们希望开启一个线程去执行一个任务,并且这个任务执行完成后有一个返回值。

@FunctionalInterface
public interface Callable<V> {/*** 处理任务并返回一个结果** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}

Callable一般是配合线程池工具ExecutorService来使用的。ExecutorService可以使用submit方法来让一个Callable接口执行。它会返回一个Future,我们通过
Future.get()就可以获取线程执行的返回结果了。

1.2 线程组和线程优先级

Java中用ThreadGroup来表示线程组,我们可以使用线程组对线程进行批量控制。

ThreadGroup和Thread的关系就如同他们的字面意思一样简单粗暴,每个Thread必然存在于一个ThreadGroup中,Thread不能独立于ThreadGroup存在。执行main()方法线程的名字是main,如果在new Thread时没有显式指定,那么默认将父线程(当前执行new Thread的线程)线程组设置为自己的线程组。

ThreadGroup管理着它下面的Thread,ThreadGroup是一个标准的向下引用的树状结构,这样设计的原因是防止”上级”线程被”下级”线程引用而无法有效地被GC回收。

Java中线程优先级可以指定,范围是1~10。但是并不是所有的操作系统都支持10级优先级的划分(比如有些操作系统只支持3级划分:低,中,高),Java只是给操作系统一个优先级的参考值,线程最终在操作系统的优先级是多少还是由操作系统决定。

Java默认的线程优先级为5,线程的执行顺序由调度程序来决定,线程的优先级会在线程被调用之前设定。

通常情况下,高优先级的线程将会比低优先级的线程有更高的几率得到执行。我们使用方法Thread类的setPriority()实例方法来设定线程的优先级。

Java中的优先级来说不是特别的可靠,Java程序中对线程所设置的优先级只是给操作系统一个建议,操作系统不一定会采纳。而真正的调用顺序,是由操作系统的线程调度算法决定的。

Java提供一个线程调度器来监视和控制处于RUNNABLE状态的线程。线程的调度策略采用抢占式,优先级高的线程比优先级低的线程会有更大的几率优先执行。在优先级相同的情况下,按照“先到先得”的原则。每个Java程序都有一个默认的主线程,就是通过JVM启动的第一个线程main线程。

还有一种线程称为守护线程(Daemon),守护线程默认的优先级比较低。

如果某线程是守护线程,那如果所有的非守护线程结束,这个守护线程也会自动结束。

应用场景是:当所有非守护线程结束时,结束其余的子线程(守护线程)自动关闭,就免去了还要继续关闭子线程的麻烦。

一个线程默认是非守护线程,可以通过Thread类的setDaemon(boolean on)来设置。

【一个线程必然存在于一个线程组中,那么当线程和线程组的优先级不一致的时候将会怎样呢?】

public static void main(String[] args) {ThreadGroup threadGroup = new ThreadGroup("t1");threadGroup.setMaxPriority(6);Thread thread = new Thread(threadGroup,"thread");thread.setPriority(9);System.out.println("我是线程组的优先级"+threadGroup.getMaxPriority());System.out.println("我是线程的优先级"+thread.getPriority());
}

所以,如果某个线程优先级大于线程所在线程组的最大优先级,那么该线程的优先级将会失效,取而代之的是线程组的最大优先级。

1.3 Java线程的状态及主要转化方法

Enum Thread.State
在这里插入图片描述

在这里插入图片描述
【1】反复调用同一个线程的start()方法是否可行?
【2】假如一个线程执行完毕(此时处于TERMINATED状态),再次调用这个线程的start()方法是否可行?
查看Thread类中start()方法源码,代码如下

 public synchronized void start() {//threadStatus表示处于NEW状态的线程if (threadStatus != 0)throw new IllegalThreadStateException();//通知当前线程的线程组这个线程将要启动,并添加当前线程到线程组中//当前线程组未启动线程数减少group.add(this);boolean started = false;try {start0();started = true;} finally {try {//处理启动失败的线程if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {}}}//本地方法执行线程的实际启动流程private native void start0();

在start()内部,这里有一个threadStatus的变量。如果它不等于0,调用start()是会直接抛出异常的。

我是在start()方法内部的最开始打的断点,叙述下在我这里打断点看到的结果:

测试代码如下

 @Testpublic  void testThreadState(){Thread thread = new Thread(()->{System.out.println("Thread Run...");});thread.start();thread.start();}

第一个 thread.start();执行情况如下
在这里插入图片描述
第二个 thread.start();执行情况如下
在这里插入图片描述
两个问题的答案都是不可行,在调用一次start()之后,threadStatus的值会改变(threadStatus !=0),此时再次调用start()方法会抛出IllegalThreadStateException异常。

比如,threadStatus为2代表当前线程状态为TERMINATED。

1.4 Java线程间的通信

线程同步是线程之间按照一定的顺序执行。

1.4.1 等待/通知机制

Java多线程的等待/通知机制是基于Object类的wait()方法和notify(), notifyAll()方法来实现的。

notify()方法会随机叫醒一个正在等待的线程,而notifyAll()会叫醒所有正在等待的线程。

1.4.2 信号量

JDK提供了一个类似于“信号量”功能的类Semaphore。但本文不是要介绍这个类,而是介绍一种基于volatile关键字的自己实现的信号量通信。

volitile关键字能够保证内存的可见性,如果用volitile关键字声明了一个变量,在一个线程里面改变了这个变量的值,那其它线程是立马可见更改后的值的。

【需求】让线程1输出0,然后线程2输出1,再然后线程A输出2…以此类推。我应该怎样实现呢?

 private static Object lock=new Object();private static volatile  int sign=0;static class MyThread1 implements  Runnable{@SneakyThrows@Overridepublic void run() {while (sign<5){if (sign%2==0){System.out.println("线程1--->"+sign);synchronized (lock){sign++;}}}}}static class MyThread2 implements  Runnable{@Overridepublic void run() {while (sign<5){if (sign%2!=0){System.out.println("线程2--->"+sign);synchronized (lock){sign++;}}}}}public static void main(String[] args) throws InterruptedException {Thread threadA = new Thread(new MyThread1());Thread threadB = new Thread(new MyThread2());threadA.start();threadB.start();Thread.sleep(4000);}

注意:
上面使用了一个volatile变量signal来实现了“信号量”的模型。但是volatile仅仅只线程可见的,signal++并不是一个原子操作,所以我们需要使用synchronized给它“上锁”

1.4.3 管道

管道是基于“管道流”的通信方式。JDK提供了PipedWriter、 PipedReader、 PipedOutputStream、 PipedInputStream。其中,前面两个是基于字符的,后面两个是基于字节流的。

public class PipeExample {/*** 构建一个管道读的线程*/static class ReaderThread implements  Runnable{private  PipedReader pipedReader;public ReaderThread(PipedReader pipedReader) {this.pipedReader = pipedReader;}@Overridepublic void run() {int count=0;try{//接收并输出流while ((count= pipedReader.read())!=-1){System.out.println((char)count);}} catch (IOException e) {e.printStackTrace();}}}/*** 构建一个写入管道流的线程*/static class WriterThread implements Runnable {private PipedWriter writer;public WriterThread(PipedWriter writer) {this.writer = writer;}@SneakyThrows@Overridepublic void run() {try {writer.write("qwertyui");} catch (IOException e) {e.printStackTrace();}finally {//写入管道的流必须关闭writer.close();}}}public static void main(String[] args) throws IOException, InterruptedException {PipedWriter writer = new PipedWriter();PipedReader reader = new PipedReader();// 这里注意一定要连接,才能通信writer.connect(reader);new Thread(new ReaderThread(reader)).start();Thread.sleep(1000);new Thread(new WriterThread(writer)).start();}}

我们通过线程的构造函数,传入了PipedWrite和PipedReader对象。可以简单分析一下这个示例代码的执行流程:

1:线程ReaderThread开始执行
2:线程ReaderThread使用管道reader.read()进入”阻塞“
3:线程WriterThread开始执行
4:线程WriterThread用writer.write(“XXXX”)往管道写入字符串
5:线程WriterThread使用writer.close()结束管道写入,并执行完毕
6:线程ReaderThread接受到管道输出的字符串并打印
7:线程ReaderThread执行完毕

管道通信的应用场景:使用管道多半与I/O流相关。当我们一个线程需要先另一个线程发送一个信息(比如字符串)或者文件等等时,就需要使用管道通信了。


http://chatgpt.dhexx.cn/article/vYbvve8j.shtml

相关文章

【多线程间几种通信方式】

一、使用 volatile 关键字 基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想。大致意思就是多个线程同时监听一个变量&#xff0c;当这个变量发生变化的时候 &#xff0c;线程能够感知并执行相应的业务。这也是最简单的一种实现方式 代码案例 package com.han…

线程之间的通信方式

前言 我只是个搬运工&#xff0c;尊重原作者的劳动成果&#xff0c;本文来源下列文章链接&#xff1a; https://zhuanlan.zhihu.com/p/129374075 https://blog.csdn.net/jisuanji12306/article/details/86363390 线程之间为什么要通信&#xff1f; 通信的目的是为了更好的协…

Java线程间的通信方式

文章目录 线程间通信的定义一、等待—通知&#xff08;1&#xff09;等待—通知机制的相关方法&#xff1a;&#xff08;2&#xff09;注意事项&#xff1a;&#xff08;4&#xff09;notify()方法的核心原理&#xff08;5&#xff09;等待—通知机制的经典范式&#xff08;6&a…

线程间实现通信的几种方式

目录 线程通信相关概述提出问题方式一&#xff1a;使用Object类的wait() 和 notify() 方法方式二&#xff1a;Lock 接口中的 newContition() 方法返回 Condition 对象&#xff0c;Condition 类也可以实现等待/通知模式方法三&#xff1a;使用 volatile 关键字方法四&#xff1a…

线程间的通信方式

对共享数据进行更改的时候&#xff0c;先到主内存中拷贝一份到本地内存中&#xff0c;然后进行数据的更改&#xff0c;再重新将数据刷到主内存&#xff0c;这中间的过程&#xff0c;其他线程是看不到的。 1、为什么需要线程通信 线程是操作系统调度的最小单位&#xff0c;有自…

进程和线程的几种通信方式

进程之间通信的几种方式 1. 管道&#xff1a;是内核里面的一串缓存 管道传输的数据是单向的&#xff0c;若相互进行通信的话&#xff0c;需要进行创建两个管道才行的。 2. 消息队列&#xff1a; 例如&#xff0c;A进程给B进程发送消息&#xff0c;A进程把数据放在对应的消息队…

线程的几种通信方式

目录 一、Object的wait()、notify()、notifyAll()方法 二、Condition的await()、signal()、signalAll()方法 三、CountDownLatch 四、CyclicBarrier 五、Semaphore 线程间的通信方式常用的有如下几种&#xff1a; Object的wait()、notify()、notifyAll()方法&#xff1b; …

线程间的通信方法

线程间的通信方法 1. 线程通信简介 一般而言&#xff0c;在一个应用程序&#xff08;即进程&#xff09;中&#xff0c;一个线程往往不是孤立存在的&#xff0c;常常需要和其它线程通信&#xff0c;以执行特定的任务。如主线程和次线程&#xff0c;次线程与次线程&#xff0c…

Matlab基本操作函数 abs函数

分享一下我老师大神的人工智能教程&#xff01;零基础&#xff0c;通俗易懂&#xff01;http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章。分享知识&#xff0c;造福人民&#xff0c;实现我们中华民族伟大复兴&#xff01; 1、abs函数&#xff1a;数值的绝对值和复数…

MATLAB中FFT的整理

作为一个资深的健忘症患者&#xff0c;需要把每次用都忘记的FFT问题进行整理。 FFT可将信号从时域转换到频域。 首先是一些简单常识&#xff1a; 采样周期&#xff1a;两次采样之间的时间间隔。 采样频率&#xff1a;1/采样周期。每秒采样的点数。&#xff08;注意&#xff1a…

matlab中abs函数,matlababs是什么意思 是是是什么意思

matlababs是什么意思 是是是什么意思以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! matlab 中的abs函数什么意思 编程知识 matlab中的abs(x)是去绝对值的函数 例如:x=-1.23 abs(x) ans 1.23 以上即是取了-1.23…

Matlab 用法

MATLAB基础&#xff1a; 清除命令 clc 清空命令行的命令 clf 清除当前figure中的内容 clear 清除工作区变量 close all 关闭所有图形窗口 清除命令通常放在代码最前方&#xff0c;避免其他变量或代码的干扰 变量命名规则 ①以英文字母开头&#xff0c;可包含英文字母、…

abs 三种功能及代码详解 matlab函数

1.abs函数功能 求实数的绝对值、复数的模、字符串的ASCII值 2.基本用法 abs(x)函数是对数组元素进行绝对值处理的函数。 函数的定义域包括复数。 对于复数xab*i&#xff0c;有abs(x)sqrt(a2b2)。 3.代码 clc; clear all;a -7; b 12i; abs(a…

android 屏幕坐标总结

android 屏幕坐标好多个&#xff0c;有时候傻傻分不清楚&#xff0c;经常记错&#xff0c;然后只能一个个试。尴尬&#xff5e;&#xff5e; 把它们总结下来&#xff0c;以备不时之需嘿嘿。 一、视图坐标 最外面一层是屏幕&#xff0c;左上角是坐标原点&#xff0c;向右向…

【Unity3D】世界坐标与屏幕坐标

Unity3D由于是在三维世界中编程&#xff0c;而最终的结果是需要反馈到肉眼所示的2D屏幕之上的。这就产生了一种比较需要考虑的问题&#xff0c;尤其在一些涉及屏幕与Unity3D的3D世界交互的情况。网络上对于这方面的文字&#xff0c;大部分罗列了许许多多文字与代码或者API&…

Unity世界坐标转换屏幕坐标(测试)

下面展示一下上一篇说的两种实现方式打包文件在不同分辨率下的效果 1.WorldToScreenPoint 1920 * 1080 800 * 600 2.WorldToViewportPoint 1920 * 1080 800 * 600 总结 可以看到四种情况全部都显示正确&#xff0c;我们再看一下原来的代码 public Vector3 GetScreenPositio…

Unity 屏幕坐标转UI坐标

1&#xff1a;屏幕坐标转UI坐标 首先我们来明确下三个坐标概念&#xff1a; 世界坐标&#xff1a;指的是Transform组件的position字段 UI坐标&#xff1a;指的是RectTransform组件的anchoredPosition字段 屏幕坐标&#xff1a;指的是屏幕空间的坐标 (也可以说是相机空间的坐…

经纬度转换成屏幕坐标

学期projet总结&#xff1a; 当把点的数据和线的数据读进来之后&#xff0c;为了画出地图还有最重要的一步就是把实际的经纬度转换成屏幕像素点的坐标。在找老师讨论之前&#xff0c;我在网上查资料&#xff0c;找到了下边链接的文章&#xff0c;并按照这个方法画出了地图。 …

Unity-世界坐标与屏幕坐标

transform.position.x和transform.position.y的值含义是世界坐标。 世界坐标与屏幕坐标有时一样&#xff0c;有时不同&#xff0c;这和Canvas的渲染模式有关。 Canvas共有三种渲染模式 Screen Space - Overlay (此模式UGUI层一直在最上面&#xff0c;其他例如粒子等物体一直…

Unity 世界坐标、屏幕坐标、UGUI 坐标 相互转换

Unity 世界坐标、屏幕坐标、UGUI 坐标 相互转换 坐标转换是游戏开发过程中必不可少的环节 看下图 世界坐标、屏幕坐标、UI 坐标 三种坐标系的转换过程&#xff0c;此文章中的 UI 坐标特指 UGUI 坐标 从上图可以看到&#xff0c;世界坐标 和 UI 坐标 需要通过 屏幕坐标作为中间…