[Java EE] 多线程 -- 初阶(1)

1.线程(Thread)

1.1 线程是什么?

  • 一个线程就是一个执行流 ;
  • 每个线程之间都可以按照顺序执行自己的代码 ;
  • 多个线程之间 "同时"执行着多份代码

1.2 线程的作用

①并发式编程

单核 CPU 的发展遇到了瓶颈 , 要想提高算力 , 就需要多核 CPU ; 而并发式编程能更充分的利用多核 CPU 资源

有些任务场景需要 "等待IO" , 为了让等待 IO 的时间能够取做一些其他工作 , 也需要用到并发编程

② 虽然多进程也能实现 并发编程 , 但是 线程比进程更轻量

  • 创建线程比创建进程更快
  • 销毁线程比销毁进程更快
  • 调度线程比调度进程更快

③ 线程虽然比进程轻量 , 但是还引入了 "线程池"(ThreadPool) 和 "协程"(Coroutine)

1.3 线程和进程区别

① 进程是包含线程的

  • 每个进程至少包含一个线程 , 即为主线程

② 进程和进程之间不共享内存空间 ; 同一个进程的线程之间共享同一个内存空间

③ 进程是系统分配资源的最小单位 ; 线程是系统调度的最小单位

④ 一个进程挂了一般不会影响其他进程 ; 但是一个线程挂了 , 可能把同进程内的其他线程一起带走(整个线程崩溃)

1.4 Java 线程和操作系统线程的关系

线程是操作系统的概念 ; 操作系统内核实现了线程这样的机制 , 并且对用户层提供了一些 API 供用户使用

Java 标准库中的 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装

2.创建线程

方法 1 : 继承 Thread 类

java 复制代码
class Mythread extends Thread{
    //run 相当于进程的入口
    @Override
    public void run() {
        while(true){
            System.out.println("hello thread");
            //调用这个方法会抛异常,由于这个类是继承与Thread的,run方法是继承与Thread中的run
            //子类重写方法抛出的异常,必须是父类方法异常的子类或相同类型,且不能抛出更宽泛的异常
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Mythread();//父类引用指向子类实例
        t.start();//启动线程
        //t.run();//只是调用这个方法,并不是创建进程
        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);//向上抛异常
        }
    }
}

继承 Thread 类, 直接使⽤ this 就表⽰当前线程对象的引⽤.

使用 jconsole 命令来观察线程

Java\jdk\bin 目录下(安装 jdk 的路径) 找 jconsole.exe , 并连接

Thread.sleep(1000) 会让当前进程放弃 CPU 资源 , 进入休眠状态 (毫秒), 此时 CPU 可以调度另一个线程执行

方法 2 : 实现 Runnable 接口

java 复制代码
class MyRunnable implements Runnable{

    @Override
    public void run() {
        while(true){
            System.out.println("hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class demo2 {
    public static void main(String[] args) throws InterruptedException {
        //创建 Thread 类实例 , 调用 Thread 的构造方法时 , 将 Runna 对象作为 target 参数
        Runnable myRunnable = new MyRunnable();//父类接口的引用变量指向子类实例
        Thread t = new Thread(myRunnable);
        t.start();//还是需要用到 t (Thread) 来开启线程
        while (true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

实现 Runnable 接口 , this 表示的是 MyRunnable 的引用 , 需要使用 Thread.currentThread()

方法 3 : 匿名内部类创建 Thread 子类对象

java 复制代码
public class demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
        while (true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

**new Thread(){...}**创建了 Thread 类的匿名内部类 , 并重写 run() 方法

方法 4 : 匿名内部类创建 Runnable 子类对象

java 复制代码
public class demo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

方法 5 : lambda 表达式创建 Runnable 子类对象

复制代码
public class demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while(true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

                             );
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }

3.Thread 类

3.1Thread 的常见构造方法

|-----------------------------------------------------|----------------------------|
| 方法 | 说明 |
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
| Thread(String name) | 创建线程对象 , 并命名 |
| Thread(Runnable target , String name) | 使用 Runnable 对象创建线程对象 , 并命名 |
| [ 了解 ]Thread(ThreadGroup group , Runnable target) | 线程可以被用来分组管理 , 分好的组即为线程 |

ThreadGroup : 线程组 , 把多个线程放到一个组里 , 统一针对线程里所有的线程进行一些属性设置

java 复制代码
class Mythread1 extends Thread{
    public Mythread1(String name) {
        super(name);
    }

    //run 相当于进程的入口
    @Override
    public void run() {
        while(true){
            System.out.println("hello thread");
            //调用这个方法会抛异常,由于这个类是继承与Thread的,run方法是继承与Thread中的run
            //子类重写方法抛出的异常,必须是父类方法异常的子类或相同类型,且不能抛出更宽泛的异常
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class demo6 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while(true){
                System.out.println("hello t1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"这是线程一");//线程命名为th1
        t1.start();

        Mythread1 t2 = new Mythread1("这是线程二");
        t2.start();
    }
}

3.2 Thread 的常见属性

|--------|-----------------|
| 属性 | 获取方法 |
| ID | getId() |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台线程 | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | isInterrupted() |

  • ID : 线程的唯一标识符 , 不同线程不会重复
  • 名称 : 是用于调试工具(jstack)区分线程 , 可以通过构造方法或者 setName()自定义
  • 状态 : 表示线程当前所处的状态 (新建 , 可运行 , 阻塞 , 无限期等待 , 超时等待 , 终止)

|-------------------------|----------------------------------------------------------------------------------------------|
| 状态 | 含义及场景 |
| NEW(新建) | 线程已创建(如new Thread()),但未调用start() 方法,未与操作系统线程关联。 |
| RUNNABLE(可运行) | 线程调用start()后进入此状态,包含 "就绪"(等待 CPU 调度)和 "运行中"(正在执行run()方法)两种子状态。 |
| BLOCKED(阻塞) | 线程因竞争synchronized锁失败而等待锁,例如多个线程争抢同一把对象锁时,未抢到的线程进入此状态。 |
| WAITING(无限期等待) | 线程无超时地等待被其他线程唤醒,如调用Object.wait()(无超时)、Thread.join()(无超时)、LockSupport.park()等方法后进入此状态。 |
| TIMED_WAITING(超时等待) | 线程在指定时间内等待,超时后自动唤醒,如调用Thread.sleep(time)Object.wait(time)Thread.join(time)等方法后进入此状态。 |
| TERMINATED(终止) | 线程的run()方法执行完毕(正常结束或因异常终止),生命周期结束,无法再被启动。 |

  • 优先级 : 范围 1-10(默认 5 ) , 优先级高的线程理论上更容易被调度到
  • 是否后台线程 : 后台线程(守护线程) 随 JVM 中所有非后台线程结束而终止 , 典型如垃圾回收线程
  • 是否存活 : 判断 run()方法是否执行完毕 , start()后到 run()结束前返回 true
  • 是否被中断 : 检测线程中断状态( 不会清楚中断标记) , 需要结合 interrupt()方法理解中断机制

部分示例 :

isAlive();
java 复制代码
public class demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        System.out.println(t.isAlive());//还没有start(),一定是false
        t.start();
        while(true){
            System.out.println(t.isAlive());//在run()结束之前,一定是true
            Thread.sleep(1000);
        }
    }
}
设置后台线程 setDaemon();
java 复制代码
public class demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.setDaemon(true);//设置t为后台线程,会随着非后台线程的结束而终止,也就是说main线程结束t线程也结束
        //如果不加该语句,则main线程结束t线程继续执行,互不干扰
        t.start();
        for (int i = 0; i < 3; i++) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
        System.out.println("main 线程结束");
    }
}

自己编写的线程一般默认为前台线程

3.3 启动线程 -- start()

run() 是线程的入口 , 不需要手动调用 , **start()**是调用系统 api

调用 start()方法 , 才真的是在操作系统的底层创建了一个线程

java 复制代码
public class demo11 {
    public static void main(String[] args) {
        Thread t = new Thread(()->
                              System.out.println("线程1"));
        //t.start();
        //t.run();
        t.start();
    }
}

每个 Thread 对象 , 只能 start 一次 , 否则抛异常

创建 t 线程的逻辑在 main 中 , 因此一定是先执行 main 线程

3.4 中断线程

让线程的入口方法 , 尽快结束

方法 1 : 引入一个isFinished 变量(引入自定义的变量来作为标志位)

java 复制代码
public class Demo10 {
    private static boolean isFinished = false;

    public static void main(String[] args) throws InterruptedException {


        //boolean isFinished = false;
        //如果将变量放在这里,会触发lambda变量捕获
        //此时如果main线程执行完了,对应的isFinished就销毁了
        //从而改成内部类访问外部类成员
        Thread t = new Thread(() -> {
            //内部类访问外部类成员
            while (!isFinished) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread 结束");

        });
        t.start();

        Thread.sleep(3000);
        isFinished = true;
    }
}

方法 2 : 方法 2 : 使⽤ Thread.interrupted() 或者Thread.currentThread().isInterrupted() 代替⾃定义标志位

Thread.currentThread() : 用来返回线程的的名称

|-------------------------------------|-------------------------------------|
| 方法 | 说明 |
| public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标识位 |
| public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清楚标志位 |
| public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,标志后不清楚标志位 |

java 复制代码
public class demo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){//判定线程是否被终止了
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //throw new RuntimeException(e)
                    break;
                }
            }
            System.out.println("t线程结束");
        });

        t.start();

        Thread.sleep(3000);
        System.out.println("main 线程尝试终止 t 线程");
        t.interrupt();//主动去终止线程,由于线程中大部分时间都在休眠,此时还会唤醒sleep这样的阻塞方法,捕获到异常从而终止,针对异常的处理,使用break结束循环
        //会改变isInterrupted()的值为true
    }
}

使用 t.interrupted()方法通知线程结束

thread 收到通知的⽅式有两种:

  1. 如果线程因为调⽤ wait/join/sleep 等⽅法⽽阻塞挂起,则以 InterruptedException 异常的形式通
    知,清除中断标志
    当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽
    略这个异常, 也可以跳出循环结束线程.
  2. 否则,只是内部的⼀个中断标志被设置,thread 可以通过
    Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
    这种⽅式通知收到的更及时,即使线程正在 sleep 也可以⻢上收到
异常处理
如果异常不做处理

此处认为这个线程可以是立即结束(break),等会结束(在 catch 中的 break 前编写需要的代码),还是不结束(在 catch 中不写 break 也就是忽略这个终止信号)

3.5 等待一个线程 -- join()

|-----------------------------------------|----------------------|
| 方法 | 说明 |
| public void join() | 等待线程结束 |
| public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
| public void join(long millis,int nanos) | 同理,但可以更高精度 |

java 复制代码
public class demo12 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("thread 线程结束");
        });
        System.out.println("hello main");


        t.start();
        System.out.println("main 线程");
        t.join(3000);//main线程最多等3秒
        System.out.println("main 线程结束");
    }
}

在 main 线程中调用 , 意味着让 main 线程等待 t 线程执行完毕在接着执行 main

3.6 获取当前线程的引用(前面使用过)

|---------------------------------------|-------------|
| 方法 | 说明 |
| public static Thread currentThread(); | 返回当前线程对象的引用 |

哪个线程调用这个方法 , 返回哪个线程的引用

3.7 休眠当前线程(前面使用过)

|----------------------------------------------------------------------------|------------------|
| 方法 | 说明 |
| public static void sleep(long millis)throws InterruptedException | 休眠当前线程 millis 毫秒 |
| public static void sleep(long millis,int nanos)throws InterruptedException | 同理,获取更高精度 |

由于线程的调度不可控 , 所以这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间

**sleep(0)**特殊写法,让当前线程放弃 CPU 资源 , 等待操作系统重新调度

相关推荐
陌路202 小时前
Linux33 网络编程-多线程TCP并发
网络·算法
西岭千秋雪_4 小时前
Zookeeper实现分布式锁
java·分布式·后端·zookeeper·wpf
MarcoPage5 小时前
Python 字典推导式入门:一行构建键值对映射
java·linux·python
脸大是真的好~5 小时前
黑马JAVAWeb-11 请求参数为数组-XML自动封装-XML手动封装-增删改查-全局异常处理-单独异常分别处理
java
Hello.Reader7 小时前
Data Sink定义、参数与可落地示例
java·前端·网络
2401_837088508 小时前
stringRedisTemplate.opsForHash().entries
java·redis
星释9 小时前
Rust 练习册 :Pythagorean Triplet与数学算法
开发语言·算法·rust
星释9 小时前
Rust 练习册 :Nth Prime与素数算法
开发语言·算法·rust
lkbhua莱克瓦2410 小时前
Java基础——集合进阶3
java·开发语言·笔记