当前位置: 首页 > article >正文

java多线程基础

前言

我们现代的CPU一般都是多核CPU,合理使用进程/线程可以更大程度上利用CPU的性能,并发编程也是一种常用的编程策略。相比于进程,线程的开销跟小更轻量,这也是java所推荐的方案。本文将从进程的概念谈起,介绍进程的创建以及简单的使用

什么是线程以及我们为什么需要线程

在前面,我们介绍过进程的概念,线程和进程的功能和概念几乎是相同的。进程的创建销毁各种开销是巨大的,线程则是一个轻量级进程,简单来说线程是一个执行流

线程是从属于进程的,一个进程可以拥有多个线程,这些线程会共享父进程的资源,这也是为什么线程比进程轻量的原因。前面我们介绍操作系统使用PCB描述进程再组织,这是建立在一个进程中只有一个线程中情况,一个进程多个线程则需要时使用多个PCB分表描述进程内的线程,这些线程分别独立调度。

但线程也并非越多越好,在极端的情况下,线程之间会竞争从而降低整体的效率。进程间是独立的,但是线程如果不妥善处理崩溃可能会导致整个进程崩溃

java和线程

线程是通过操作系统提供一组API进行操作,java为了自身跨平台的特性,封装了这些操作提供了一组java操作线程的API——Thread类

我们可以先简单见一见如何使用这个类创建线程

class MyThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("hello thread");
        }
    }
}

public class Demo1 {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
        while (true) {
            System.out.println("hello main");
        }
    }
}

继承Thread类,重run方法,这个方法代表这个线程要执行的代码逻辑,在主线程(main函数就是主线程)中创建这个自定义线程的对象,调用对象的start方法,这是系统会自动创建线程执行run里面的代码逻辑。如果是对象.run不是创建线程而是调用方法

我们可以通过jdk通过的jconsole观察线程的状态。这个程序位于jdk安装目录bin下,会描述所有java进程中的线程状态,包括进程名称,状态调用栈此类的信息。这可以方便我们后期对代码的调试

其他创建线程的方案

前面我们使用继承Thread的方式创建线程,除此之外我们在介绍几种常见的创建方案

实现Runnable

class RunnableDemo implements Runnable {

    @Override
    public void run() {
        while (true) {
            System.out.println("hello thread");
        }
    }
}

public class Demo2 {
    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        thread.start();
        while (true) {
            System.out.println("hello main");
        }
    }
}

实现Runnable接口,创建Runnable对象,将对象传入到Thread中

注意 Runnable接口只是一种解耦合的方案,用来提取任务逻辑,具体的创建线程还是Thread实现的

匿名内部类

上面不管是对Thread类的继承还是对Runnable的实现都可以使用匿名内部类来实现简写

public class Demo3 {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while (true){
                    System.out.println("hello thread");
                }
            }
        };
        thread.start();
        while (true){
            System.out.println("hello main");
        }
    }
}
public class Demo4 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("Runnable");
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        while (true) {
            System.out.println("main");
        }
    }
}

lambda

其中Runnable接口使用@FunctionalInterface注解声明为函数式接口,使用lambda表达式可以达到同样的效果

public class Deno5 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true){
                System.out.println("hello thread");
            }
        });
        thread.start();
        while (true){
            System.out.println("hello main");
        }
    }
}

Thread类常见方法属性

构造方法

常见的构造方法主要是以下四种

        //无参构造 创建线程
        Thread thread1 = new Thread();
        //创建制定名称的线程
        Thread thread2 = new Thread("name");
        //传入 runnable接口
        Thread thread3 = new Thread(()->{
            while (true){
                System.out.println("hello thread");
            }
        });
        //传入runnable接口和线程名称
        Thread thread4 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println("hello thread");
                }
            }
        },"name");

属性

简单介绍一些常用的属性和设置/获取属性的方法

        //声明线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                }
            }
        });
        //启动线程
        thread.start();
        // 获取线程id
        thread.getId();
        // 获取线程名称
        thread.getName();
        // 设置线程名称
        thread.setName("name");
        // 获取线程状态 返回值是状态的枚举类
        thread.getState();
        // 获取线程优先级
        thread.getPriority();
        // 设置线程优先级
        // 优先级范围是1-10
        // 优先级越大,线程抢占cpu的概率越大
        // 可以使用Thread.MAX_PRIORITY和Thread.MIN_PRIORITY这种常量来设置优先级
        thread.setPriority(10);
        // 判断线程是否存活
        // 可能对象存在但是线程已经被销毁或者还未启动
        thread.isAlive();
        // 判断是否是后台线程(守护线程)
        // 默认是前台线程,前台线程会一直运行,后台线程会在进程结束的时候结束
        thread.isDaemon();
        // 判断线程是否中断
        thread.isInterrupted();

启动线程

前面我们介绍过,线程的启动必须调用start。run只是实现了线程需要执行的代码逻辑,但是线程的创建还是由start方法内部实现

中断线程

java中中断线程只能是让一个线程的run方法快速执行完成

我们先来看一段代码

public class Demo8 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                System.out.println("hello thread");
            }
        });
        thread.start();
        while (true) {
            System.out.println("hello main");
        }
    }
}

这里的thread线程是个前台死循环的线程,main线程想要中断这个thread线程应该如何操作呢

我们可以这样

public class Demo8 {

    public static Boolean  flag = true;
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (flag) {
                System.out.println("hello thread");
            }
        });
        System.out.println("thread线程启动");
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = false;
        System.out.println("thread线程中断");
    }
}

注意,如果将flag声明为main的变量时,lambda虽然会自动捕获这个变量,但是要求这个变量是final修饰或者实际上是final的变量,我们这种写法显然是不够优雅的。并且如果线程休眠必须等待休眠结束才能中断线程。那么有没有办法可以优雅又可以在休眠中唤醒呢

可以使用Thread.interrupted() 或者 Thread.currentThread().isInterrupted()

Thread.currentThread()会得到当前线程的对象

public class Demo9 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
            }
        });
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread.interrupt();
        System.out.println("线程中断");
    }
}

特别的如果线程正在sleep则会离开抛出一个InterruptedException,抛出错误的同时清除标志位

等待线程

有点时候某个线程需要等待另外一个线程的工作全部完成,才会执行自己的逻辑

可以使用join等待另外一个线程执行完成

public class Demo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("main");
    }
}

注意这个A线程的这个方法在B线程中被调用则 B线程等待A线程调度完成才会继续向下执行

默认无参是死等的情况,也可以指定最大的等待时间

线程状态

线程状态在一个Thread.State的枚举类中 我们可以先答应看一下有哪些属性

public class Demo11 {
    public static void main(String[] args) {
        for (Thread.State state : Thread.State.values()) {
            System.out.println(state);
        }
    }
}

运行结果:
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

NEW 对象存在但是没用调用start方法 线程还未被创建

TERMINATED 对象存在但是线程已经被销毁了

RUNNABLE 就绪状态 正在CPU上执行或者在CPU调度队列中

TIMED_WAITING 由sleep触发的固定时间的阻塞

WAITING 由wait这种不确定时间的阻塞

BLOCKED 由于锁竞争导致的阻塞

结语

以上便是今天的全部内容。如果有帮助到你,请给我一个免费的赞。

因为这对我很重要。

编程世界的小比特,希望与大家一起无限进步。

感谢阅读!


http://www.kler.cn/a/589585.html

相关文章:

  • Ubuntu零基础学习---基础指令
  • 依赖倒置 DIP、依赖注入 DI、控制反转 IoC 和工厂模式
  • Kotlin-inline函数特效
  • 【从0到1搞懂大模型】RNN基础(4)
  • Spring组件初始化扩展点:BeanPostProcessor
  • MacOS 15.3.1 安装 GPG 提示Error: unknown or unsupported macOS version: :dunno
  • Java---SpringMVC(2)
  • 自然语言处理(NLP)核心技术深度解析
  • ReLU对决Leaky ReLU:深度学习的生死博弈
  • 系统盘的制作
  • [蓝桥杯](布尔类型dfs)全球变暖
  • Ollama + CherryStudio:构建本地私有知识库
  • CC45.【C++ Cont】STL中的哈希表及练习
  • 定时器‘PWM和串口通信(20250317)
  • cesium 实现万级管网数据渲染,及pickImageryLayerFeatures原生方法改写
  • 网络性能指标
  • 五金打磨车间降温设备都哪些?
  • 在 TypeScript 中,两个看似相同的字符串用 `==` 比较返回 `false`
  • 【Agent】OpenManus-Prompt组件详细分析
  • C3P0数据库连接池技术详解及实战