Java多线程编程

news2024/11/26 2:40:50

Java多线程编程

前言

  • Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
  • 这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
  • 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

一个线程的生命周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。下图显示了一个线程完整的生命周期:

在这里插入图片描述

  • 新建状态:使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  • 就绪状态:当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
  • 运行状态:如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  • 阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  • 死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

线程的优先级

  • 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
    Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY );
  • 默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5);
  • 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台;

创建一个线程

Java 提供了三种创建线程的方法:

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 Callable 和 Future 创建线程;

实现 Runnable 接口

通过实现 Runnable 接口来创建线程:
创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如下:

public void run()

你可以重写该方法,重要的是理解的 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。
在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。 Thread 定义了几个构造方法,下面的这个是我们经常使用的:

Thread(Runnable threadOb,String threadName);

这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。
新线程创建之后,你调用它的 start() 方法它才会运行。

void start();

下面是一个创建线程并开始让它执行的实例:

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;
   
   RunnableDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 让线程睡眠一会
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
 
public class TestThread {
 
   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo( "Thread-1");
      R1.start();
      
      RunnableDemo R2 = new RunnableDemo( "Thread-2");
      R2.start();
   }   
}

结果为:

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

继承Thread

创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。
继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例:

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 让线程睡眠一会
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
 
public class TestThread {
 
   public static void main(String args[]) {
      ThreadDemo T1 = new ThreadDemo( "Thread-1");
      T1.start();
      
      ThreadDemo T2 = new ThreadDemo( "Thread-2");
      T2.start();
   }   
}

结果为:

Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

Callable 和 Future 创建线程

  1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
public class CallableThreadTest implements Callable<Integer> {
    public static void main(String[] args)  
    {  
        CallableThreadTest ctt = new CallableThreadTest();  
        FutureTask<Integer> ft = new FutureTask<>(ctt);  
        for(int i = 0;i < 100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);  
            if(i==20)  
            {  
                new Thread(ft,"有返回值的线程").start();  
            }  
        }  
        try  
        {  
            System.out.println("子线程的返回值:"+ft.get());  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        } catch (ExecutionException e)  
        {  
            e.printStackTrace();  
        }  
  
    }
    @Override  
    public Integer call() throws Exception  
    {  
        int i = 0;  
        for(;i<100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" "+i);  
        }  
        return i;  
    }  
}

创建线程的三种方式的对比

  1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

线程的几个主要概念

在多线程编程时,你需要了解以下几个概念:

  • 线程同步
  • 线程间通信
  • 线程死锁
  • 线程控制:挂起、停止和恢复

多线程的使用

  1. 有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
  2. 通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。
  3. 请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!

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

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

相关文章

新快报:十年聚焦,巨杉数据库打造中国基础软件的“原创力”

广东省级主流媒体新快报策划“非凡十年&#xff0c;广州答卷”专题&#xff0c;关注十年来广州的“原创力量”&#xff0c;作为土生土长的广州基础软件创新企业&#xff0c;巨杉数据库十年聚焦&#xff0c;从零打造原生分布式数据库&#xff0c;获得逾百家金融银行客户认可&…

STM32——SDIO的学习(驱动SD卡)(实战篇)

目录 一、SDIO寄存器 1.1 SDIO电源控制寄存器(SDIO_POWER) 1.2 SDIO时钟控制寄存器(SDIO_CLKCR) 1.3 SDIO参数寄存器(SDIO_ARG) 1.4 SDIO命令寄存器(SDIO_CMD) 1.5 SDIO命令响应寄存器(SDIO_RESPCMD) 1.6 SDIO响应 1..4 寄存器(SDIO_RESPx) 1.7 SDIO数据定时器寄存器(S…

C语言程序设计题/C语言计算机二级考前押题版

C语言程序设计题/C语言计算机二级考试押题版 与 数位 和 数 有关 求max与min 任意四个数 运算符和表达式版本 #include <stdio.h> int main( ) {int a,b,c,d;int max,min;printf("please input 4 integers:");scanf("%d%d%d%d", &a, &b, …

ESP32通过 WiFi 传输视频

概述 这是ESP32 WiFi视频流的入门教程。ESP32-CAM 是一款带有ESP32-S 芯片的小型摄像头模块,除了OV2640 相机和多个用于连接外围设备的 GPIO 外,它还具有一个microSD 卡插槽,可用于存储相机拍摄的图像。 ESP32-CAM 简介 ESP32-CAM 是一款采用ESP32-S 芯片的超小型相机模组…

HP EliteBook 840 G6电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件配置 硬件型号驱动情况 主板HP Elitebook 840 G6 处理器Intel(R) Core(TM) i7-8565U CPU 1.80GHz (max 4.60Ghz) Kaby Lake R已驱动 内存SK Hynix 32 GB (2x16) 2…

QT 设计ROS GUI界面订阅和发布话题

QT 设计ROS GUI界面订阅和发布话题 主要参考下面的博客 ROS项目开发实战&#xff08;三&#xff09;——使用QT进行ROS的GUI界面设计&#xff08;详细教程附代码&#xff01;&#xff01;&#xff01;&#xff09; Qt ROS 相关配置请看上一篇博客 首先建立工作空间和功能包&a…

深度学习训练营之船类识别

深度学习训练营之船类识别 原文链接环境介绍前言收获前置工作设置GPU导入图片数据预处理 数据可视化配置数据集数据显示 构建模型模型训练编译训练模型 结果可视化(模型评估)损失值可视化混淆矩阵各项指标评估 原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 …

腾讯云轻量应用服务器的端口怎么开通?

腾讯云轻量应用服务器怎么使用&#xff1f;端口在哪开启&#xff1f;在防火墙中可以开启端口号。 腾讯云轻量应用服务器端口怎么开通&#xff1f;在轻量服务器管理控制台的防火墙中开启端口&#xff0c;如果是CVM云服务器在安全组中开通&#xff0c;阿腾云以轻量应用服务器开通…

Python+Socket实现多人聊天室,功能:好友聊天、群聊、图片、表情、文件等

一、项目简介 本项目主要基于python实现的多人聊天室&#xff0c;主要的功能如下&#xff1a; 登录注册添加好友与好友进行私聊创建群聊邀请/申请加入群聊聊天发送图片聊天发送表情聊天发送文件聊天记录保存在本地中聊天过程中发送的文件保存本地 二、环境介绍 python3.8my…

浅谈 RISC-V 软件开发生态之 IDE

软件开发者是芯片公司非常重要的资产&#xff0c;CPU做出来是不够的&#xff0c;要让更多的软件开发者用这颗芯片才是成功。国际大厂们都有一只较大的软件团队&#xff0c;在做面向开发者的软件工具和SDK等。--张先轶博士&#xff1a;为什么RISC-V需要共建软件生态? 前言 目前…

VsCode终端无法使用conda切换环境的问题

一.问题描述: windows的cmd可以正常使用conda切换环境 为了方便想使用vscode的终端,但是报错: PS C:\Users\admin\Desktop\MyProject> conda activate objection1.8.4 CommandNotFoundError: Your shell has not been properly configured to use conda activate. invocati…

智能排班系统 【管理系统功能、操作说明——下篇】

文章目录 页面与功能展示排班日历月视图&#xff08;按职位查询&#xff09;月视图&#xff08;按员工查询&#xff09;周视图 排班任务管理创建排班计算任务设置任务的排班规则设置工作日客流量导入任务计算查看任务结果发布任务任务多算法计算 页面与功能展示 排班日历 在排…

vcruntime140.dll丢失的解决方法?教你如何修复好dll文件

Vcruntime140.dll文件是Windows操作系统中非常重要的一个动态链接库文件&#xff0c;用于支持使用Microsoft Visual C编译器创建的应用程序的运行。当Windows系统中的vcruntime140.dll文件丢失时&#xff0c;可能会导致某些应用程序无法正常启动。在尝试启动应用程序时&#xf…

Android HCE开发

我们来详细说明一下关于不同模式下的AID响应问题&#xff08;前提&#xff1a;一个手机&#xff0c;手机上有A、B两个HCE APP&#xff0c;通过读卡器向手机发送APDU选择指令&#xff09; 1、A和B的应用AID设置的都是payment模式&#xff0c; 只有手机当前选定的默认支付APP会响…

基于Faster rcnn pytorch的遥感图像检测

基于Faster rcnn pytorch的遥感图像检测 代码&#xff1a;https://github.com/jwyang/faster-rcnn.pytorch/tree/pytorch-1.0 数据集 使用RSOD遥感数据集&#xff0c;VOC的数据格式如下&#xff1a; RSOD是一个开放的目标检测数据集&#xff0c;用于遥感图像中的目标检测。…

图片类型转换,url,File,Base64,Blob

一&#xff0c;图片url转化为文件 function urlToFile(url, imageName) {return new Promise((resolve, reject) > {var blob nullvar xhr new XMLHttpRequest()xhr.open(GET, url)xhr.setRequestHeader(Accept, image/png)xhr.responseType blobxhr.onload () > {b…

JVM内存模型和结构详解

JVM内存模型和Java内存模型都是面试的热点问题&#xff0c;名字看感觉都差不多&#xff0c;实际上他们之间差别还是挺大的。 通俗点说&#xff0c;JVM内存结构是与JVM的内部存储结构相关&#xff0c;而Java内存模型是与多线程编程相关mikechen。 什么是JVM JVM是Java Virtual …

Redis缓存雪崩及解决办法

缓存雪崩 1.缓存雪崩是指在同- -时段大量的缓存key同时失效或者Redis服务宕机&#xff0c;导致大量请求到 达数据库&#xff0c;带来巨大压力。 2.解决方案: ◆给不同的Key的TTL添加随机值 ◆利用Redis集群提高服务的可用性 ◆给缓存业务添加降级限流策略 降级可做为系统的保底…

java程序1补充:从键盘输入圆的半径,求圆的周长和面积(简易与交互两版)

编写一个java程序&#xff0c;从键盘输入圆的半径&#xff0c;求圆的周长和面积&#xff0c;并输出。 要求&#xff1a; &#xff08;1&#xff09;半径仅考虑int型正整数&#xff0c;并综合利用所学较好地处理异常输入&#xff0c;包括非法整数、负整数输入时的处理。输入半…

网络编程套接字基本概念认识

目录 认识端口号 认识TCP协议 认识UDP协议 网络字节序 socket编程接口 socket 常见API sockaddr结构 认识端口号 端口号(port)是传输层协议的内容 端口号是一个2字节16位的整数; 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理; IP地址 …