【JAVA】多线程:一文快速了解多线程

目录

  • 【JAVA】多线程:一文快速了解多线程
    • 知识点
    • JAVA多线程
      • 进程和线程的区别?
        • 进程和线程的区别体现在以下几个方面:
        • 进程和线程的选择取决以下几点:
    • 线程的生命周期
    • 线程的优先级
    • 守护线程和用户线程
    • Java Thread的API
    • Thread 创建线程的方法
      • 通过实现 Runnable 接口;
      • 通过继承 Thread 类本身;
      • 通过 Callable 和 Future 创建线程。
    • 总结

个人主页: 【⭐️个人主页】
需要您的【 点赞+关注】支持


【JAVA】多线程:一文快速了解多线程_第1张图片

【JAVA】多线程:一文快速了解多线程

知识点

JAVA多线程

进程和线程的区别?

【JAVA】多线程:一文快速了解多线程_第2张图片

进程是系统中正在运行的一个程序,程序一旦运行就是进程。

进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。

【JAVA】多线程:一文快速了解多线程_第3张图片

一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。
线程与进程的一个主要区别是, 同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存(一个进程无法直接访问另一进程的内存)。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写这些栈内存。

线程是进程的一个实体,是进程的一条执行路径。

线程是进程的一个特定执行路径。当一个线程修改了进程的资源,它的兄弟线程可以立即看到这种变化。

进程和线程的区别体现在以下几个方面:
  1. 地址空间和其他资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其他进程内不可见。
  2. 通信:进程间通信IPC(管道,信号量,共享内存,消息队列),线程间可以直接独写进程数据段(如全局变量)来进程通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  3. 调度和切换:线程上下文切换比进程上下文切换快得多。
  4. 在多线程OS中,进程不是一个可执行的实体。
进程和线程的选择取决以下几点:

1.需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程的代价是很大的。

2.线程的切换速度快,所以在需要大量计算,切换频繁时使用线程,还有耗时的操作时用使用线程可提高应用程序的响应。

3.因为对CPU系统的效率使用上线程更占优势,所以可能要发展到多机分布的用进程,多核分布用线程。

4.并行操作时用线程,如C/S架构的服务器端并发线程响应用户的请求。

5.需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。

线程的生命周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
【JAVA】多线程:一文快速了解多线程_第4张图片

  • 新建状态:

    使用 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虚拟机启动时,通常会有一个非守护进程线程(通常调用某个指定类的名为main的方法)。 Java虚拟机继续执行线程,直到发生以下任一情况:

  • 已调用类Runtime的exit方法,并且安全管理器已允许执行退出操作。
  • 通过调用run方法返回或抛出超出run方法传播的异常,所有非守护程序线程的线程都已死亡。

用户线程 :是指在程序中通过Thread类创建的线程,默认情况下是用户线程。用户线程的生命周期和程序主线程相同,当程序主线程结束时,所有的用户线程也会自动结束,用户线程会组织应用程序的退出

守护线程 :是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:

  1. thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
  2. 在Daemon线程中产生的新线程也是Daemon的。
  3. 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

Java Thread的API

Thread Api 说明
start() 启动线程并调用 run() 方法。在 start() 方法被调用后,新线程会在一个单独的执行路径上运行。
sleep(long millis) 暂停当前线程的执行,让其他线程有机会执行。这是一个静态方法,可以在任何地方使用。当线程被暂停时,它将放弃 CPU 控制权,但它的保持不会释放。
yield() 告诉当前线程放弃CPU资源,以便其他线程可以运行。如果没有其他线程等待执行,当前线程将继续运行,不释放资源,由运行状态变为就绪状态.
作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证
yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield() 不会导致阻塞
join()/join(long millis) 等待线程执行终止,当前线程里调用其它线程的join方法后,当前线程进入WAITING/TIMED_WAITING 状态,直到被调用线程执行完毕或者 millis 时间到,当前线程进入就绪状态
注意:在Java中,join()方法不会释放锁。如果一个线程在获得了某个对象的锁之后调用了该对象的join()方法,那么它仍然持有该对象的锁。
wait() 和 notify() wait()notify():用于实现线程之间的通信。当一个线程等待某些条件时,可以调用 wait() 方法暂停自己的执行,等待其他线程调用 notify() 方法来通知它恢复执行。
注意:wait() notify() 必须在 synchronized 块中使用,以确保线程安全;当前线程调用对象的 wait() 方法,当前线程释放对象锁
interrupt() 中断线程,例如,当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。如果线程A因为调用了wait系列函数、join方法或者sleep方法而被阻塞挂起,这时候若线程B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException异常而返回。
isInterrupted() 检测当前线程是否被中断,如果被中断(中断标志位为true), 则返回true,否则返回false。
interrupted() 作者:喝咖啡的工匠 链接:https://juejin.cn/post/7210216031536578618 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。检测当前线程是否被中断,如果被中断,则返回true,否则返回false。与isInterrupted不同的是,该方法如果发现当前线程被中断,则会清除中断标志,并且该方法是static方法,可以通过Thread类直接调用。

Thread 创建线程的方法

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

通过实现 Runnable 接口;

public class RunnableThread implements Runnable{
 @Override
 public void run() {
  System.out.println("Runnable 实现: 线程 Runnable");
 }
}
public static void main(String[] args) {
       
        // runnable
        new Thread(new RunnableThread()).start();

    }

通过继承 Thread 类本身;

public class AThread extends Thread {

    @Override
    public void run() {
        System.out.println("hello : 我是A Thread");
    }
}
    public static void main(String[] args) {
       
        // extends Thread
        AThread aThread = new AThread();
        aThread.start();

    }

通过 Callable 和 Future 创建线程。

Callable 接口是一个具有类型参数的泛型接口,它的 call() 方法可以返回一个结果,并且可能会抛出异常。Future 接口则是一个表示异步计算的结果的接口,它提供了检查计算是否完成、等待计算完成和获取计算结果的方法:

public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        Thread.sleep(3000);
        return "Hello, Callable!";
    }
}
    public static void main(String[] args) {
   
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new MyCallable());
        try {
            String result = future.get();
            System.out.println("Callable result: " + result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }


    }

总结

多线程能够帮助我们有效利用CPU的性能处理任务。提高了处理能力和并发能力。但是多线程(并发)会引起线程数据安全访问问题。需要我们对安全问题有足够认识,该文章只是介绍了Java中的多线程编程的理论基础知识。线程安全我们下一章再讨论~

你可能感兴趣的:(Java开发知识,java,开发语言,1024程序员节)