[Java多线程 1] 多线程基础

news2024/11/25 15:06:43

在Java 技术中,多线程依旧是一个离不开的话题,掌握多线程才能对一些高并发技术理解透彻。同时多线程也需要有一定的操作系统基础,在其理论上进行学习,会对调度情况、线程情况有更多的了解。当然这一块也常常作为Java面试的重点,必须深刻理解与掌握。

本次是关于多线程的基础部分,随后将有更多关于多线程的知识点进行整理与汇总。由于本人目前学业繁忙,故平时更新随缘。

目录

  • 进程与线程
  • 多线程启动
  • 线程状态
  • sleep 与 yield
  • 线程优先级
  • 守护进程

进程与线程

​ 进程是操作系统动态执行的基本单元,运行在自己地址空间的自包容程序。进程可以看成线程的容器,而线程又可以看作进程中的执行路径。线程使得程序控制流的多个分支可以执行在一个进程中,它们共享这个进程范围内的所有资源。在大多数OS中,把线程作为时序调度基本单元
​ 多线程使用可以提高一个复杂应用的性能。Java 机制是抢占式的。
​ 那么到底什么是线程呢?简单总结如下:线程(Thread)的字面意思是线路,即应用程序(进程)中的程序执行线路。Java虚拟机允 许一个应用程序中可以同时并发存在多条程序执行线路。每个线程都 有一个优先级属性,优先级别高的线程,可能会被CPU优先执行。

多线程启动

一般其有两种启动方式:实现Runnable 接口,继承Thread 类并重写run方法。
使用Runnable 接口来提供,实现runnable 接口并重写run 方法,再将Runnable 实现对象传给Thread 类。通常,这个实现接口是一个更好的选择,提高程序的灵活性和扩展性,在后面的线程池调用中也使用Runnable 来表示执行。

public class TestRun {
    public static void main(String[] args) {
        Runa a = new Runa();
        new Thread(a).start();
    }
}

// 实现接口
class Runa implements Runnable {
    public void run() {
        System.out.println("Execute");
    }
}

当然也可以继承Thread 类并重写run方法:

public class TestTrea {
    public static void main(String[] args) {
        athread c = new athread();
        c.start();
    }
}

class athread extends Thread {
    public void run() {
        System.out.println("running...");
    }
}

需要注意的是,start方法并不代表线程启动的顺序,顺序都是不定的!为什么呢?因为任务的执行靠CPU,而处理器采用分片轮询方式执行任务,所有任务都是抢占式执行模式,说明任务不排序。

多线程标识
Thread 类用于管理线程、如设置线程优先级、设置Daemon属性,读取线程名字和ID,中断线程等。为了管理线程,每个线程启动后都会生成一个唯一的标识符,并且在生命周期保持不变。当线程终止时候,该ID可以重用。

public static void main(String[] args) {
        for(int i=0;i<5;i++) {
            Runa c = new Runa();
            // 启动线程,申请执行任务
            Thread a = new Thread(c);
            System.out.println(a.getId());
            System.out.println(a.getName());
        }
    }

Thread 和 Runnable :Runnable 接口表示线程要执行的任务,当其中run方法执行时,表示进程就在激活状态。

public interface Runnable {
    
    public abstract void run();
}

Thread 类默认实现Runnable 接口,构造方法的重载形式允许传入Runnable 接口对象作为任务。

class Thread implements Runnable {
    private Runnable target;
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

run() 与 start ()

class Thread implements Runnable {
   
    public void run() {
        
    }
    public synchronized void start() {}
    
}

调用start 方法,使对象开始执行任务,这会触发Java 虚拟机调用当前线程对象的run方法。调用start方法,将会导致两个线程并发运行,一个是调用start的当前线程,一个是执行run的线程。如果反复调用start,非法,不会产生更多的线程,导致 IllegalThreadStateException异常。

调用start 方法后,触发了JVM 底层调用run方法,如果主动调用Thread对象的run方法,并不能启动一个新线程。

创建Thread类实例,首先会执行**registerNatives()**方法,它在静态代码块中加载。线程的启动、运行、生命期管理和调度等都高度依赖于操作系统,Java本身并不具备与底层操作系统交互的能力。因此线程的底层操作都使用了native方法,registerNatives()就是用C语言 编写的底层线程注册方法。
无论通过哪种构造方法创建线程,都需要首先调用init()方法,初始化线程环境:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

在init 方法中,实现了:设置线程名称、将新线程的父线程设置为当前线程、获取系统的安全管理并获得线程组、获取权限检查。

线程状态

在不同时期有不同的状态,在Thread 类通过内部枚举类State保存:

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIME_WAITING,
    TERMINATED;
} 
// 通过getState方法来进行获取

NEW 状态:新建状态,一个已创建但是没有启动的线程。(没有start())
RUNNABLE 状态表示一个线程正在Java 虚拟机运行,调用start方法后切换到此状态。
BLOCKED:阻塞状态,表示当前线程正在阻塞等待获得监视器锁,当一个线程要访问被其他线程synchronized 锁定的资源时候,当前线程需要阻塞等待。

Thread a = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (cc) { 
                        while(true) {
                            
                        }
                }
            }) ;

WAITING:等待状态,当调用Object类的wait方法,Thread 的join 方法 (无超时设置),LockSupport类的park()方法。处于等待状态的线程,正在等待另一个线程去完成特殊操作,等待Object 对象调用notify或notifyAll方法,一个线程对象调用join方法,则会等待线程终止任务。

TIMED_WAITING 状态:表示线程处于定时等待状态。有设置wait,join,sleep方法,parkUntil,pakNanos方法,在指定时间内没有调用Object对象的notify 就会触发超时等待结束

WAITING、TIMED_WAITING、BLOCKED这几个线程状态, 都会使当前线程处于停顿状态,因此容易混淆。下面简单总结一下这 些状态之间的区别: (1)Thread.sleep()不会释放占有的对象锁,因此会持续占用 CPU。 (2)Object.wait()会释放占有的对象锁,不会占用CPU。 (3)BLOCKED使当前线程进入阻塞后,为了抢占对象监视器锁,一般操作系统都会给这个线程持续的CPU使用权。 (4)LockSupport.park()底层调用UNSAFE.park()方法实现, 它没有使用对象监视器锁,不会占用CPU。

TERMINATED 表示线程为完结状态,当线程完成run方法中的任务,或者中断线程状态会变为terminated。

Java 线程状态转化:

sleep 与 yield

Thread 类的sleep 方法,使当前执行的线程以指定的毫秒数暂时停止执行,具体停止时间取决于系统定时器和调度程序的精度和准确性。调用sleep方法不会使线程丢失监视器所有权,因此当前线程仍用cpu 分区。

    public static native void sleep(long millis) throws InterruptedException;

测试代码:

class Runa implements Runnable {
    public void run() {
        try{
            long begin = System.currentTimeMillis();
            System.out.println("Integer:");
            for(int i=0;i<10;i++) {
                TimeUnit.SECONDS.sleep(1); // 需要捕获异常
                System.out.println(i);
            }
        }catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

线程让步yield

yield 方法对线程调度发出一个暗示,即当前线程愿意让出正在使用的处理器。调度程序可以响应暗示请求也可以忽略。可以从running状态为runnable。
注意:yield 是一个暗示,没有机制会保证采纳。线程调度是Java线程机制的底层对象,可以把CPU使用权从一个线程转移到另一个线程。如果计算机是多核处理器,那么分配线程到不同处理器执行任务要依赖线程调度器。
下面来进行代码测试:使用sleep作为线程延时

public class ListOff implements Runnable{

    public int countDown =5;

    @Override
    public void run() {
        while(countDown-- >0) {
            String info = Thread.currentThread().getId()+"#"+countDown;
            System.out.println(info);
            try{
                TimeUnit.MILLISECONDS.sleep(100);
            }catch (Exception e) {

            }
        }
    }

    public static void main(String[] args) {
        ListOff lf = new ListOff(); // 创建一个倒计时器,两个线程可以同时使用
        //关键原因countDown 唯一,两个线程可能同时访问这块内存,可以通过加锁方式解决。
        new Thread(lf).start();
        new Thread(lf).start();
    }
}

把sleep代码修改为yield ,三次结果都是正确的,起到了线程让步(此处没有使用锁)

public void run() {
        while(countDown-- >0) {
            String info = Thread.currentThread().getId()+"#"+countDown;
            System.out.println(info);
            Thread.yield();
        }
    }

线程优先级

每个线程都有一个优先级,具有较高优先级可以优先获得CPU使用权。实际上,JDK有10个优先级,但是这与操作系统不可以建立映射关系,比如Windows 有7个线程优先级,所以在Java一般使用下面三种优先级设置。

public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

可以通过Thread类中setPriority 方法对线程优先级进行设置,参考如下:

public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }

注意:如果设置的线程优先级小于1或者大于10 都将抛出IllegalArgumentException异常。不应该过分依赖于线程优先级,理论上线程优先级高的会优先执行。

具有较高优先级的线程会优先得到调度系统资源分配,优先级调度和底层操作系统有密切的关系,在各个平台表现不一致。当调用yield 方法时,会给线程调度器一个暗示,即优先级高的其他线程或相同优先级的其他线程,都可以优先获得CPU分片。

例如:创建六个进程,每个进程都计算足够量级的浮点运算,目的是让线程调度来得及介入。其中将1个线程设置为最高。

public class FloatArithmetic implements  Runnable{
    private  int pri;
    public FloatArithmetic(int pri) {
        this.pri = pri;
    }
    public void run() {
        BigDecimal value = new BigDecimal("0");
        // 按照参数传递优先级
        Thread.currentThread().setPriority(pri);
        BigDecimal pi = new BigDecimal(Math.PI);
        BigDecimal e = new BigDecimal(Math.E);
        //足够耗时计算,使得任务调度可以反应
        for(int i=0;i<3000;i++) {
            for(int j=0;j<3000;j++) {
                value = value.add(pi.add(e).divide(pi,4));
            }
        }
        Thread self = Thread.currentThread();
        System.out.println("Number:"+self.getId()+" Priority"+self.getPriority());
    }

    public static void main(String[] args) {
        new Thread(new FloatArithmetic(Thread.MIN_PRIORITY)).start();
        new Thread(new FloatArithmetic(Thread.NORM_PRIORITY)).start();
        new Thread(new FloatArithmetic(Thread.MAX_PRIORITY)).start();
    }
}

运行结果将按照优先级高低来显示。

例:使用多线程模拟窗口售票,非常经典的案例!

public class TicketTask implements  Runnable{
    private Integer ticket = 30;
    public void run() {
        while(this.ticket>0) {
            synchronized (this) {
                if(ticket>0) {
                    System.out.println("No"+Thread.currentThread().getId()+"sell:"+ticket);
                    ticket--;
                    try{
                        Thread.sleep(100);
                    }catch (Exception ex) {

                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        TicketTask task = new TicketTask();
        Thread t1 = new Thread(task);
        t1.setPriority(Thread.MIN_PRIORITY);
        Thread t2 = new Thread(task);
        Thread t3 = new Thread(task);
        t3.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
        System.out.println("t1"+t1.getId());
        System.out.println("t2"+t2.getId());
        System.out.println("t3"+t3.getId());
    }
}

守护进程

在Java 线程有两种线程,分别是用户线程与守护线程(Daemon)。所谓守护进程,是指程序运行时候在后台提供一种通用服务的线程。比如,垃圾回收就是守护者。Daemon线程与用户线程在使用时候没有任何区别,唯一的不同是:当所有用户线程结束时候,程序也会终止,Java虚拟机不管是否存在守护线程,都会退出。
调用Thread 对象的setDaemon方法,可以把用户线程标记为守护者,调用isDaemon可以判断是否为一个守护线程。

在调用守护线程的时候需要注意:
(1)setDaemon()方法必须在start()方法之前设置,否则会抛 出一个IllegalThreadState-Exception异常。不能把正在运行的常规线程设置为守护线程。 (2)在守护线程Daemon中产生的新线程也是守护线程,存在 着继承性。 (3)守护线程应该永远不去访问固有资源,如文件、数据库, 因为它会在任何时候甚至在一个操作的中间发生中断。 (4)守护线程通常都使用while(true)的死循环来持续执行任务。

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

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

相关文章

HNU工训中心:元器件及测量基础实验报告

工训中心的牛马实验 1.实验目的 1.熟悉测量验证常用元器件参数、 并采用替代法(测量回路电流)测量其伏安特性的方法。 2.熟悉测量误差及减小测量误差注意事项 2.实验仪器和器材 1.实验仪器. 直流稳压电源型号:IT6302 台式多用表型号:UT805A 2.实验( 箱)器材 电路实验箱…

针对序列级和词元级应用微调BERT(需修改)

对于序列级和词元级自然语言处理应用&#xff0c;BERT只需要最小的架构改变&#xff08;额外的全连接层&#xff09;&#xff0c;如单个文本分类&#xff08;例如&#xff0c;情感分析和测试语言可接受性&#xff09;、文本对分类或回归&#xff08;例如&#xff0c;自然语言推…

sdut pta查找表

7-1 电话聊天狂人 给定大量手机用户通话记录&#xff0c;找出其中通话次数最多的聊天狂人。 输入格式: 输入首先给出正整数N&#xff08;≤105&#xff09;&#xff0c;为通话记录条数。随后N行&#xff0c;每行给出一条通话记录。简单起见&#xff0c;这里只列出拨出方和接…

SpringBoot:SpringBoot整合Junit 和 MyBatis(3)

SpringBoot整合Junit 和 MyBatis1. SpringBoot整合Junit2. SpringBoot整合MyBatis2.1 定义SpringBoot项目2.2 定义dao接口2.3 定义service层2.4 定义controller层2.5 配置yml/yaml文件2.6 postman测试1. SpringBoot整合Junit 在com.example.service下创建BookService接口 publ…

华为OD机试题,用 Java 解【两数之和绝对值最小】问题

最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…

面向对象拓展贴

1. 类和对象的内存分配机制 1.1 分配机制  Java 内存的结构分析 栈&#xff1a; 一般存放基本数据类型(局部变量)堆&#xff1a; 存放对象(Cat cat , 数组等)方法区&#xff1a;常量池(常量&#xff0c;比如字符串)&#xff0c; 类加载信息示意图 [Cat (name, age, price)]…

安全狗出席2023中国网络和数据安全产业高峰论坛

2月23日&#xff0c;由工业和信息化部、四川省人民政府主办的“2023中国网络和数据安全产业高峰论坛”在成都顺利开幕。 作为国内云原生安全领导厂商&#xff0c;安全狗也出席了此次活动。 在此次活动中&#xff0c;“2022年网络安全技术应用试点示范项目授牌仪式”环节引起业…

TwinCAT3第三方伺服电机——汇川SV660N使用

目录 一、第三方伺服在TC3中配置和使用 二、xml文件拷贝 ​编辑 三、IO中扫描伺服 四、工程测试 五、汇川伺服参数设置说明 一、第三方伺服在TC3中配置和使用 在倍福控制系统中使用第三方伺服可以参见本人另一篇博客&#xff0c;有详细教程说明。本文仅仅对SV660N伺服设置…

表格形式的Sarsa与Q_learning算法

环境如下&#xff1a; 这是一个简单的环境&#xff0c;绿色方块代表终点&#xff0c;白色方块代表可行点&#xff0c;灰色方块代表陷阱 用Sarsa算法和Q_learning算法训练得到value表格 代码如下&#xff1a; (jupyter notebook上的代码&#xff0c;所以顺序看起来有点儿奇怪) …

【博学谷学习记录】超强总结,用心分享丨人工智能 Linux常用软件安装 CenOS 7 arm 安装 MySQL8

目录环境说明虚拟机安装MySQL下载步骤1.卸载系统自带的mariadb-lib2.上传安装包并解压3.按顺序安装4.初始化数据库5.目录授权&#xff0c;否则启动失败6.启动msyql服务7.查看msyql服务的状态8.在/var/log/mysqld.log下查看临时密码9.用临时密码登录到数据库10.执行下列mysql命令…

给打算转行IC的同学几点建议,来听听工程师怎么说?

“我不是相关专业的&#xff0c;也没什么IC设计的基础&#xff0c;能转IC设计岗吗&#xff1f;” “感觉自己学比较乱&#xff0c;不知道到底怎么学&#xff1f;没有项目经验怎么办&#xff1f;” 每一个想转IC设计岗位的同学都或多或少地遇到过这样的问题&#xff0c;有着找…

使用 Postman 实现 API 自动化测试

目录&#xff1a;导读 背景介绍 名词解析 使用说明 执行 API 测试 集成 CI 实现 API 自动化测试 写在最后 背景介绍 相信大部分开发人员和测试人员对 postman 都十分熟悉&#xff0c;对于开发人员和测试人员而言&#xff0c;使用 postman 来编写和保存测试用例会是一种比…

Vuex 状态管理器(vuex安装与配置、state、mutations、actions、getters、module)全解

文章目录知识点Vuex 的简介Vuex 的安装与配置Vuex 的核心概念核心概念之&#xff1a;State核心概念之&#xff1a;Mutations核心概念之&#xff1a;Actions核心概念之&#xff1a;GettersVuex 规则核心概念之&#xff1a;Module实验总结知识点 Vuex 的简介Vuex 的安装与配置Vu…

UE实现相机飞行效果CesiumForUnreal之DynamicPawn飞行原理浅析

文章目录 1.实现目标2.实现过程2.1 FlyTo实现原理与代码2.2 DynamicPawn飞行原理3.参考资料1.实现目标 基于CesiumForUnreal的Dynamic Pawn实现飞行效果GIF动图: 2.实现过程 实现原理较为简单,基于CesiumForUnreal插件中DynamicPawn中的Camera实现相关功能。其中FlyTo直接通…

人工智能的几个研究方向

人工智能主要研究内容是&#xff1a;分布式人工智能与多智能主体系统、人工思维模型、知识系统、知识发现与数据挖掘、遗传与演化计算、人工生命、人工智能应用等等。 其中热门研究有以下几种。 一、计算机视觉 就包括图像识别&#xff0c;视频识别&#xff0c;具体应用有人…

接口自动化测试用例详解

phpunit 接口自动化测试系列 Post接口自动化测试用例 Post方式的接口是上传接口&#xff0c;需要对接口头部进行封装&#xff0c;所以没有办法在浏览器下直接调用&#xff0c;但是可以用Curl命令的-d参数传递接口需要的参数。当然我们还以众筹网的登录接口为例&#xff0c;讲…

Qt 事件机制

【1】事件 事件是可以被控件识别的操作。如按下确定按钮、选择某个单选按钮或复选框。 每种控件有自己可识别的事件&#xff0c;如窗体的加载、单击、双击等事件&#xff0c;编辑框&#xff08;文本框&#xff09;的文本改变事件等等。 事件就是用户对窗口上各种组件的操作。…

速锐得适配北汽EX系列电动汽车CAN总线应用于公务分时租赁

过去的几年&#xff0c;我们看到整个分时租赁业务出现断崖式下跌&#xff0c;这是我们看到这种市场情况&#xff0c;是必然&#xff0c;也是出乎意料。原本很多融资后的出行公司、大牌的出行服务商的分时租赁业务&#xff0c;受各种影响不得不转型成其他出行服务。例如&#xf…

图像处理实战--Opencv实现人像迁移

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 今天来学习一下如何使用Opencv实现人像迁移&#xff0c;欢迎大家一起参与探讨交流~ 本文目录&#xff1a;一、实验要求二、实验环境三、实验原理及操作1.照片准备2.图像增强3.实现美颜功能4.背景虚化5.图像二值化处理6.人…

C++ 实现pcm 转wav

输入音PCM 属性&#xff1a; 16k采样率 16位深 单声道 #include <iostream> #include <vector> #include <fstream> using namespace std; /* wav音频头部格式 */ typedef struct _wave_pcm_hdr { char riff[4]; // "RIFF&…