Java多线程通关———基础知识

简介: Java多线程通关———基础知识

掌握基础知识。


线程与进程


1 线程:进程中负责程序执行的执行单元

线程本身依靠程序进行运行


线程是程序中的顺序控制流,只能使用分配给程序的资源和环境

2 进程:执行中的程序

一个进程至少包含一个线程


3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程


4 多线程:在一个程序中运行多个任务

目的是更好地使用CPU资源


线程的实现

继承Thread类

java.lang包中定义, 继承Thread类必须重写run()方法


classMyThread extendsThread{
    privatestatic int num = 0;
    publicMyThread(){
        num++;
    }
    @Override
    publicvoid run() {
        System.out.println("主动创建的第"+num+"个线程");
    }
}

创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。


publicclass Test {
    publicstatic void main(String[] args)  {
        MyThread thread = newMyThread();
        thread.start();
    }
}
classMyThread extendsThread{
    privatestatic int num = 0;
    publicMyThread(){
        num++;
    }
    @Override
    publicvoid run() {
        System.out.println("主动创建的第"+num+"个线程");
    }
}


1)thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;

2)虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。

通过Thread的源代码,我们现在对其主要的的一些方法进行讲解一下

     native关键字 -  native是与C++联合开发的时候用的!使用native关键字说明这个方法是原生函数,也就是说这个方法是用C/C++语言实现的,并且被编译成dll相关组件,由java来调用。所以从上面的Thread类源代码中可以看到,有好多是调用了原生的函数。

     构造方法 - Thread有一组构造方法,具体指定了线程名称(name)线程组(ThreadGroup)接口类(Runnable)栈大小(stackSize)等参数 具体如下:


public Thread()
public Thread(Runnable target)
Thread(Runnable target, AccessControlContext acc)
public Thread(ThreadGroup group, Runnable target)
public Thread(String name)
public Thread(ThreadGroup group, String name)
public Thread(Runnable target, String name)
public Thread(ThreadGroup group, Runnable target, String name)
public Thread(ThreadGroup group, Runnable target, String name, long stackSize)

   isAlive() - 方法isAlive()是判断当前的线程是否处于活动状态。而这个活动状态指的是:线程已经启动且尚未终止,如正在运行,准备开始运行的状态,都认为线程是“存活”的。

   sleep() - 在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。

   getId() - 取得线程的唯一标识。每个线程在初始化的过程中都会调用nextThreadID方法 获取到一个唯一标识。


private static long threadSeqNumber;
private static synchronized long nextThreadID() {
   return ++threadSeqNumber;
}

    在一个进程中,线程的ID是唯一的。

    停止线程 - 停止线程是在多线程开始时很重要的技术点,而停止线程在Java中并不像break语句那样干脆,需要一些技巧性的处理。


   在Java中有以下3种方法可以终止正在运行的线程。

   使用退出标志,使线程正常退出,即当run方法完成后线程终止。

   使用stop方法强行终止线程,但是不推荐使用该方法,因为stop和suspend及resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果。

使用interrupt方法中断线程。

  暂停线程 -  暂停线程意味着此线程还可以恢复运行。使用suspend()方法暂停线程,resume()方法恢复线程的执行。

   yield - yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但是放弃的时间不确定,有可能刚放弃,马上又获得CPU时间片了。线程优先级 -  在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资料较多,也就是CPU优先执行优先级较高的线程对象中的任务。在Thread中,我们使用setPriority()方法设置优先级别。


java的线程优先级分为1~10这10个等级
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

     线程优先级具有继承特性, 比如A线程启动B线程,则B线程的优先级与A是一样的。                     优先级具有规则性,虽然我们使用setPriority()方法设置了优先级,但是真正执行的过程中,不会保证优先级高的线程绝对比优先级低的线程优先完成。即CPU尽量将执行资源让给优先级比较高的线程。

   *优先级具有随机性,具优先级较高的线程不一定每一次都先执行完。

    守护线程 - 在Java线程中有两种线程,一种是用户线程,另一种是守护线程守护线程是一种特殊的线程,它的特性有“陪伴”的含义,当进程中不存在非守护线程了,则守护线程自动销毁。

    典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程则没有存在的必要了,自动销毁。

    只要当前JVM实例中存在任何一个非守护线程没有结束,守护线程就在工作,只有当最后一个非守护线程结束时,守护线程才随着JVM一同结束工作。

    通过调用Thread.setDaemon(true)设置是否为守护线程。


实现Runnable接口

在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。

下面是一个例子:


publicclass Test {
    publicstatic void main(String[] args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyRunnable runnable = newMyRunnable();
        Thread thread = newThread(runnable);
        thread.start();
    }
}
classMyRunnable implementsRunnable{
    publicMyRunnable() {
    }
    @Override
    publicvoid run() {
        System.out.println("子线程ID:"+Thread.currentThread().getId());
    }
}

     Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,这根普通的方法调用没有任何区别。

     事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。

    在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。

     使用ExecutorService、Callable、Future实现有返回结果的多线程。


线程的状态

     在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解。

    创建(new)状态: 准备好了一个多线程的对象

    就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度

    运行(running)状态: 执行run()方法

    阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用

    终止(dead)状态: 线程销毁

    当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。

    线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。

  注:sleep和wait的区别:

  sleep是Thread类的方法,wait是Object类中定义的方法.

  Thread.sleep不会导致锁行为的改变, 如果当前线程是拥有锁的, 那么Thread.sleep不会让线程释放锁.

  Thread.sleep和Object.wait都会暂停当前的线程. OS会将执行时间分配给其它线程. 区别是, 调用wait后, 需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间.


上下文切换

    对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。

    由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。

    因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

    说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行

    虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。

目录
相关文章
|
16天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
142 60
【Java并发】【线程池】带你从0-1入门线程池
|
5天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
55 23
|
12天前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
81 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
1月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
106 14
|
1月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
57 13
|
1月前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
2月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
126 17
|
3月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
2月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
3月前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。

热门文章

最新文章