地推平台去哪里找,深圳网站建设优化,中小企业网络规划与设计论文,w7自己做网站一#xff1a;认识线程
1.1 线程的概念
线程是操作系统中执行的最小单位#xff0c;它是进程中的一个实体。一个进程可以包含多个线程#xff0c;并且这些线程共享进程的资源#xff0c;如内存、文件句柄等#xff0c;但每个线程有自己的独立执行流程和栈空间。
线程在…一认识线程
1.1 线程的概念
线程是操作系统中执行的最小单位它是进程中的一个实体。一个进程可以包含多个线程并且这些线程共享进程的资源如内存、文件句柄等但每个线程有自己的独立执行流程和栈空间。
线程在操作系统的调度和执行过程中担任重要角色一个线程就是一个 “执行流”. 每个线程之间都可以按照顺序执行自己的代码. 它可以独立执行特定任务也可以与其他线程通过同步机制进行协作。通常一个进程中的多个线程可以并发执行共享相同的上下文和资源从而提高系统的并发处理和资源利用率。
1.2 并行和并发
并发和并行是计算机领域中两个常用的概念。
并发指的是在同一时刻只能执行一个任务但是通过快速切换和时间片轮转等技术可以让多个任务交替执行给人一种同时进行的感觉。在并发的情况下多个任务之间可能是交替执行的。
并行则指的是多个任务在同一时刻同时执行每个任务拥有自己的执行资源比如独立的 CPU 核心。在并行的情况下多个任务可以同时进行每个任务都在独立的执行路径上进行处理。
简单来说如果多个任务交替执行就是并发如果多个任务同时执行就是并行。
但是因为并行和并发难以感知所以我们把并行和并发这两个概念统称为并发
1.3 为啥要有线程
首先, “并发编程” 成为 “刚需”.
单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源.有些任务场景需要 “等待 IO”, 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.
其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.
创建线程比创建进程更快.销毁线程比销毁进程更快.调度线程比调度进程更快.
最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 “线程池”(ThreadPool) 和 “协程”(Coroutine)关于线程池我们后面再介绍.
1.4进程和线程的区别
进程是包含线程的. 每个进程至少有一个线程存在即主线程。进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.进程是系统分配资源的最小单位线程是系统调度的最小单位。 线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.
二线程操作
2.1创建线程
2.1.1 方法1 继承 Thread 类
继承 Thread 来创建一个线程类.
class MyThread extends Thread {Overridepublic void run() {System.out.println(这里是线程运行的代码);}
}注意run方法中写的是这个线程要执行的任务一个线程在调用了start()方法之后才算是真正创建了。
创建 MyThread 类的实例
MyThread t new MyThread();调用 start 方法启动线程
t.start(); // 线程开始运行2.2.2 方法2 实现 Runnable 接口
实现 Runnable 接口
class MyRunnable implements Runnable {Overridepublic void run() {System.out.println(这里是线程运行的代码);}
}创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
Thread t new Thread(new MyRunnable());调用 start 方法
t.start(); // 线程开始运行2.2.3两种方法的区别
这两种写法有一些区别 继承 Thread 类当我们继承 Thread 类并重写 run() 方法时this 关键字表示的是当前线程对象的引用即 new 出来的线程对象本身。我们可以直接调用该对象的方法比如 this.sleep()。 实现 Runnable 接口当我们实现 Runnable 接口并创建了一个 Thread 对象来运行该 Runnable 对象时this 关键字表示的是实现了 Runnable 接口的类的实例即 MyRunnable 的引用。我们不能直接调用 this.sleep()而是要使用 Thread.currentThread().sleep()。
总体上说两种写法都可以创建线程并实现线程的运行逻辑不同之处在于 this 表示的对象引用不同。
2.2.4其他变形
匿名内部类创建 Thread 子类对象
// 使用匿名类创建 Thread 子类对象
Thread t1 new Thread() {Overridepublic void run() {System.out.println(使用匿名类创建 Thread 子类对象);}
};匿名内部类创建 Runnable 子类对象
// 使用匿名类创建 Runnable 子类对象
Thread t2 new Thread(new Runnable() {Overridepublic void run() {System.out.println(使用匿名类创建 Runnable 子类对象);}
});lambda 表达式创建 Runnable 子类对象
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 new Thread(() - System.out.println(使用匿名类创建 Thread 子类对象));
Thread t4 new Thread(() - {System.out.println(使用匿名类创建 Thread 子类对象);
});通过这几种方法就可以创建一个线程了需要注意的是一个线程在调用了start()方法之后才算是真正创建了。调用start()方法会导致线程进入就绪状态并且系统会为该线程分配执行资源使得它可以开始执行run()方法中的代码。
只有调用start()方法才会创建一个新的线程run()方法只是分配了这个线程的任务是什么不会创建新的线程。所以当我们使用多线程编程时要确保通过调用start()方法来启动线程以保证线程的正确创建和执行。
三Thread 类及常见方法
Thread 类是 JVM 用来管理线程的一个类换句话说每个线程都有一个唯一的 Thread 对象与之关联。
2.1Thread 的常见构造方法
方法说明Thread()创建线程对象Thread(Runnable target)使用 Runnable 对象创建线程对象Thread(String name)创建线程对象并命名Thread(Runnable target, String name)使用 Runnable 对象创建线程对象并命名Thread(ThreadGroup group, Runnable target)线程可以被用来分组管理分好的组即为线程组这个目前我们了解即可
Thread t1 new Thread();
Thread t2 new Thread(new MyRunnable());
Thread t3 new Thread(这是我的名字);
Thread t4 new Thread(new MyRunnable(), 这是我的名字);2.2 Thread 的几个常见属性
属性获取方法IDgetId()名称getName()状态getState()优先级getPriority()是否后台线程isDaemon()是否存活isAlive()是否被中断isInterrupted()
ID 是线程的唯一标识不同线程不会重复名称是各种调试工具用到状态表示线程当前所处的一个情况下面我们会进一步说明优先级高的线程理论上来说更容易被调度到关于后台线程需要记住一点JVM会在一个进程的所有非后台线程结束后才会结束运行。是否存活即简单的理解为 run 方法是否运行结束了
2.3 线程中断问题
线程的中断问题下面我们进一步说明
interrupt()、interrupted() 和 isInterrupted() 方法是 Java 中用于线程中断的相关方法它们有着不同的功能和用法。下面我将逐一解释它们的区别并提供相应的代码示例。
interrupt() 方法 interrupt() 方法是用于中断线程的方法。它并不会真正地中断线程而是给线程设置一个中断标志表示线程被请求中断。具体来说当调用线程的 interrupt() 方法时如果线程处于阻塞状态如 sleep、wait、join 等方法就会抛出 InterruptedException 异常并清除中断状态接着结束睡眠如果线程未处于阻塞状态仅仅是设置中断标志并不是真正的中断线程。线程的中断由线程决定线程可以通过检查中断标志来决定是否中断自己的执行。
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记
以下是一个示例代码演示了如何使用 interrupt() 方法中断线程
public class MyThread extends Thread {Overridepublic void run() {while (!Thread.currentThread().isInterrupted()) {// 线程任务逻辑// ...}}
}public static void main(String[] args) {MyThread thread new MyThread();thread.start();// 等待一段时间后中断线程try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt();
}注意当sleep被唤醒之后sleep会自动把 isInterrupted()标志位给清空true - false 注意异常的抛出会清除线程的中断标志位但并不会立即停止线程的执行。它只会在线程的指定时间段内抛出异常告诉线程当前处于中断状态然后线程有机会从阻塞状态恢复并在异常被捕获之后继续执行。
这种设计的目的是为了给开发者一个处理线程中断的机会。当线程被中断后开发者可以根据具体业务需求来决定如何处理中断因此当线程被InterruptedException异常唤醒时isInterrupted()的返回结果会变为false, 要不要结束线程取决于 catch 中代码的写法. 可以选择 忽略这个异常, 也可以跳出循环结束线程
interrupted() 方法 interrupted() 方法是一个静态方法用于判断当前线程是否被中断并返回中断状态。而且在判断中断状态后还会自动清除中断标志。如果线程在判断中断状态时没有被中断则返回 false如果线程在判断中断状态时被中断则返回 true。
以下是一个示例代码演示了如何使用 interrupted() 方法判断当前线程是否被中断
public class ThreadDemo {private static class MyRunnable implements Runnable {Overridepublic void run() {for (int i 0; i 10; i) {System.out.println(Thread.interrupted());}}}public static void main(String[] args) throws InterruptedException {MyRunnable target new MyRunnable();Thread thread new Thread(target, 李四);thread.start();thread.interrupt();}
}true // 只有一开始是 true后边都是 false因为标志位被清
false
false
false
false
false
false
false
false
false
注意interrupted() 方法会清除中断状态即使线程未被中断。
isInterrupted() 方法
isInterrupted() 方法是一个Java中的线程方法用于检查当前线程是否被中断。它返回一个boolean值表示线程是否被中断。
当线程被中断时isInterrupted() 方法会返回true。但是需要注意的是调用isInterrupted() 方法并不会清除线程的中断状态。
下面是一个示例代码演示如何使用isInterrupted() 方法
public class ThreadDemo {private static class MyRunnable implements Runnable {Overridepublic void run() {for (int i 0; i 10; i) {
System.out.println(Thread.currentThread().isInterrupted());}}}public static void main(String[] args) throws InterruptedException {MyRunnable target new MyRunnable();Thread thread new Thread(target, 李四);thread.start();thread.interrupt();}
}true // 全部是 true因为标志位没有被清
true
true
true
true
true
true
true
true
true下面是这三个方法的总结
方法说明public void interrupt()中断对象关联的线程如果线程正在阻塞则以异常方式通知否则设置标志位public static boolean interrupted()判断当前线程的中断标志位是否设置调用后清除标志位public boolean isInterrupted()判断对象关联的线程的标志位是否设置调用后不清除标志位
2.4线程启动
之前我们已经看到了如何通过覆写 run 方法创建一个线程对象但线程对象被创建出来并不意味着线程就开始运行了调用 start 方法, 才真的在操作系统的底层创建出一个线程.
覆写 run 方法是提供给线程要做的事情的指令清单线程对象可以认为是把 李四、王五叫过来了而调用 start() 方法就是喊一声”行动起来“线程才真正独立去执行了。 2.5 等待一个线程
因为线程是并发执行的线程的调度是抢占式执行的所以操作系统对于线程调用的顺序的不知道的无法判断哪个线程先结束因此java提供了join方法。
在Java中join()方法是Thread类的一个方法它允许一个线程等待另一个线程的完成。当一个线程调用另一个线程的join()方法时调用线程将被阻塞直到被调用的线程执行完毕。
方法说明public void join()等待线程结束public void join(long millis)等待线程结束最多等 millis 毫秒public void join(long millis, int nanos)同理但可以更高精度
下面是代码示例
public class JoinExample {public static void main(String[] args) throws InterruptedException {Thread thread new Thread(() - {try {// 模拟线程执行耗时的操作Thread.sleep(2000);System.out.println(子线程执行完毕);} catch (InterruptedException e) {e.printStackTrace();}});thread.start(); // 启动子线程thread.join(); // 主线程等待子线程执行完毕System.out.println(主线程继续执行);}
}
在上面的代码中主线程启动子线程后调用了子线程的join()方法这样主线程就会等待子线程执行完毕后再继续执行。在子线程中我们通过模拟一个耗时的操作线程休眠2秒来演示子线程的执行过程。当子线程执行完毕后主线程才会继续执行并输出主线程继续执行。
2.6获取当前线程引用
方法说明public static Thread currentThread();返回当前线程对象的引用
这个方法我们非常熟悉了
public class ThreadDemo {public static void main(String[] args) {Thread thread Thread.currentThread();System.out.println(thread.getName());}
}2.7 休眠当前线程
也是我们比较熟悉一组方法有一点要记得因为线程的调度是不可控的所以这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。
方法说明public static void sleep(long millis) throws InterruptedException休眠当前线程 millis毫秒public static void sleep(long millis, int nanos) throws InterruptedException可以更高精度的休眠
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}四线程的状态
4.2线程的状态
线程的状态是一个枚举类型 Thread.State我们可以通过这段代码得到线程的状态
public class ThreadState {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}线程在Java中有几种不同的状态每种状态表示线程在执行过程中的不同阶段或状态。线程的状态主要有以下几种 新建状态NEW当线程对象被创建时它处于新建状态。在这个状态下线程没有被启动还未开始执行。 可运行状态RUNNABLE线程处于可运行状态意味着线程已经通过调用start()方法启动可以开始执行了。线程可能正在执行也可能在等待CPU时间片来执行或者在等待其他资源。
正在运行Running线程正在执行中。就绪Ready线程等待CPU调度具备执行条件但尚未获得CPU时间片。 阻塞状态BLOCKED线程可能因为等待获取一个排它锁synchronized关键字而进入阻塞状态。当其他线程持有锁时该线程将被阻塞只有当获取到锁时才能继续执行BLOCKED 表示等待获取锁 等待状态WAITING线程在等待其他线程的特定操作例如等待其他线程的通知或等待输入/输出操作完成WAITING 表示等待其他线程发来通知WAITING 线程在无限等待唤醒 超时等待状态TIMED_WAITING类似于等待状态但是可以设置一个超时时间当超过指定时间后线程将自动恢复到可运行状态 TIMED_WAITING 表示线程在等待唤醒等待其他线程发来通知但设置了时限。 终止状态TERMINATED线程已经完成执行或者因为异常而终止。
线程状态之间可以相互转换而且转换的过程是由Java的线程调度器负责控制的。
下面是对线程状态的比喻
NEW: 安排了工作, 还未开始行动RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.BLOCKED: 这几个都表示排队等着其他事情WAITING: 这几个都表示排队等着其他事情TIMED_WAITING: 这几个都表示排队等着其他事情TERMINATED: 工作完成了. 下面是各个状态的详细关系图 所以之前我们学过的 isAlive() 方法可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。
4.2 yield
在Java中yield()方法用于暂停当前正在执行的线程并允许其他线程运行。它是线程调度器的一部分用于实现线程间的合作和协调。
yield()方法的作用是提醒调度器当前线程可以放弃cpu控制权但是它不保证CPU资源一定会让给其他线程只是将当前线程的状态更改为就绪状态然后再次与同等或更高优先级的线程竞争CPU资源。
下面是yield()方法的一般语法
Thread.yield();以下几点要注意
yield()方法只能在线程内部调用不能在静态方法中使用。yield()方法不会释放锁即使线程调用了yield()方法它仍然持有当前获取的锁。当线程调用yield()方法后它可能会立即重新获得执行权因此yield()方法不能用来实现线程的顺序执行。
下面是一个简单的示例代码演示了yield()方法的使用
Thread t1 new Thread(new Runnable() {Overridepublic void run() {while (true) {System.out.println(张三);// 先注释掉, 再放开// Thread.yield();}}
}, t1);
t1.start();
Thread t2 new Thread(new Runnable() {Overridepublic void run() {while (true) {System.out.println(李四);}}
}, t2);
t2.start();可以看到:
不使用 yield 的时候, 张三李四大概五五开使用 yield 时, 张三的数量远远少于李四