Thread
这里写目录标题
- Thread
- 线程
- Thread 第 1 种写法
- 此外, t.start()的作用
- Thread 第 2 种写法
- Thread 第 3 种写法
- Thread 第 4 种写法
- Thread 第 5 种写法
线程
本身是操作系统提供的, 操作系统提供了 API 以让我们操作线程, JVM 就对操作系统 API 进行了封装.
线程这里, 则提供了 Thread 类, 表示线程. 利用类中的创建的实例, 以及实例里面创建的一系类方法, 我们就可以完成多线程编程了.
Thread 第 1 种写法
代码示例
package thread;
class MyThread extends Thread{//注解: 相当于是 提示编译器, 进行更严格的检查@Overridepublic void run() {//run(): 描述线程的工作System.out.println("hello thread");}
}public class Demo1 {public static void main(String[] args) {Thread t = new MyThread();t.start();}
}
结果
hello threadProcess finished with exit code 0//进程结束
上述代码中, 有 2 个线程:
- t 线程;
- main 方法所在的线程 (主线程).
但以上执行, 无法很直观的看见两个线程的执行, 我们再对其进行修改.
代码如下
package thread;
class MyThread extends Thread{//注解: 相当于是 提示编译器, 进行更严格的检查@Overridepublic void run() {//run(): 描述线程的工作while(true){System.out.println("hello thread");}}
}public class Demo1 {public static void main(String[] args) {Thread t = new MyThread();t.start();while(true){System.out.println("hello main");}}
}
运行结果是两者交替 并发 执行.
我们可以使用 jdk 中包含的 jconsole 工具来观察线程.
thread.Demo1则是我们创建的线程, 选中连接.
再点击 线程
我们可以看到, 执行一个文件, 有 15 个线程, 并且在下方能看见 main 和 Thread-0 两个线程.
我们分别点击main 与 Thread线程
如图, 我们可以看到线程的调用栈, main 在调用sleep() 方法, Thread 有一个run() 方法在调用sleep().
我们同样可以打开任务管理器
我们可以看见这一个 循环, 吃了很多的CPU资源
故而我们利用 sleep() 方法, 来让线程主动进入"阻塞状态", 主动离开 CPU, 睡眠 / 休眠 一段时间.
try {Thread.sleep(1000);//休眠1秒(单位 ms)
} catch (InterruptedException e) {throw new RuntimeException(e);
}
等时间到了之后, 线程就会解除 “阻塞状态”, 重新被调度到 CPU 上执行.
package thread;
class MyThread extends Thread{//注解: 相当于是 提示编译器, 进行更严格的检查@Overridepublic void run() {//run(): 描述线程的工作while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class Demo1 {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();t.start();while(true){System.out.println("hello main");try{Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}
此时的 CPU 占用率大幅降低.
此时涉及到一个问题
在未来实际开发中, 发现服务器程序, 消耗 CPU 资源超出预期, 如何排查这个问题.
则 先需要确认哪个线程消耗的 CPU 比较高: 利用第三方工具找到此线程, 确定以后, 进一步 排查 线程中是否有类似 “非常快速” 的循环.
然后确认 此循环是否应该这么快, 若应该, 则升级 CPU. 若不应该, 则需要在循环中引入 等待操作(不一定是sleep() ).
然而
每个线程打印可能是 main 在前, 也可能是 thread 在前, 多个线程的调度是无序的, 在操作系统内部也称为 “抢占式执行”. 充满了随机性, 正是如此, 使用多线程是难以预测的.
此外, t.start()的作用
package thread;
class MyThread extends Thread{//注解: 相当于是 提示编译器, 进行更严格的检查@Overridepublic void run() {//run(): 描述线程的工作while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class Demo1 {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();
// t.start();t.run();//这里则是没有创建线程, 就是在主线程中执行上述 run 中的循环打印.while(true){System.out.println("hello main");try{Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}
t.start() (Thread类中自带的方法):
调用 操作系统 提供的"创建线程" api,
在内核创建对应的 pcb 加入到链表中,
进一步的系统调度到这个线程了之后,
就会执行上述 run 方法中的逻辑.
当我们运行时, 只能看见起循环 Thread线程 内的打印操作, 而看不见 main线程 的打印操作.
此时的 run 和 主线程的循环是 串行执行, 不是 “并发执行”. 必须要求 run 中的循环结束, 才能继续执行到下一个循环.
像以上的 run, 定义好,而不去手动调用, 把这个方法交给系统/其他的库/其他框架调用.这样的方法称为: “回调函数”(callback function).
java数据结构, 谈到了比较强, 类似于 回调函数 的效果. PriorityQueue 优先级队列.
多线程 run 方法, 和上述两个情况本质一样, 都是回调函数.
Thread 第 2 种写法
package thread;
//Runnable: 描述一个任务
class MyRunnable implements Runnable{@Overridepublic 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){Thread t = new Thread(new MyRunnable());t.start();while (true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
第一种写法, 是 Thread 自己记录自己的操作.
第二种写法, 是 Runnable 记录操作, Thread 负责执行.
当运行内容 与 执行 两个操作分离开, 实现了解耦合操作.
在java中 解耦合 使用接口实现.
解耦合的作用: 简单来讲, 就是使代码的维护难度降低.
Thread 第 3 种写法
package thread;public class Demo3 {public static void main(String[] args) {Thread t = new Thread(){/*这里的 new Thread 并非是 new 一个 Thread, 而是几个操作融合在了一起1. 创建了一个 Thread的子类(匿名的)2. 同时创建了一个该子类id实例, 对于匿名内部类只能创建这一个实例,这个实例创建完之后, 再也拿不到这个匿名内部类了.3. 此处的子类内部重写了父类 run 方法.*/@Overridepublic void run() {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}};t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
Thread 第 4 种写法
package thread;public class Demo4 {public static void main(String[] args) {//Runnable 匿名内部类,//此处匿名内部类,只针对 Rnunable,与 Thread 无关//把 Runnable 实例, 作为参数传入到 Thread 的构造方法内Thread t = new Thread(new Runnable() {/*1.创建新的类, 实现Runnable.但类名是匿名的.2.同时创建了这个新类的实例(一次性)3.重写run方法*/@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
Thread 第 5 种写法
lambda 表达式
package thread;
//使用 lambda 表达式
public class Demo5 {public static void main(String[] args) {//此处的 lambda 就是要代替Demo4重写的 run方法Thread t = new Thread(() -> {System.out.println("hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();while(true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
以上 5 种 方法,本质上都是
1.要把线程执行的任务内容表达出来.
2.通过 Thread 的 start 来创建/启动系统种的线程.
(Thread 对象和操作系统内核中的线程是 一 一 对 应 的关系)