目录
[3.1 创建Thread⼦类对象](#3.1 创建Thread⼦类对象)
[3.2 创建Runnable⼦类对象](#3.2 创建Runnable⼦类对象)
二、多线程代码
1.继承Thread类
java
package thread;
class MyThread extends Thread {
@Override
public void 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();
//没有创建出新的线程,就是在主线程中执行run中的循环打印(就是单线程了,串行执行)
t.run();
while (true) {
System.out.println("hello main");
t.sleep(1000);
}
}
}
-
上述代码中其实有两个线程,一个t线程,一个main线程(主线程:JVM进程启动的时候,自己创建的线程)
-
重写Thread方法中的run方法:描述线程需要完成的工作
- 只是定义好,把这个方法的调用交给系统/其他的库/其他框架调用,(回调函数)
-
sleep方法:让当前线程主动进入"阻塞"状态,主动放弃在CPU上的执行,时间到了,才会解除,重新被调度到CPU上执行
-
start方法:调用操作系统提供创建线程的API,在内核中创建对应的PCB,并且把PCB加入到链表中,进一步的系统调度到这个线程之后,就会执行上述run方法的逻辑
-
Q1:如果开发中,发现你负责的服务器程序,消耗CPU资源超出预期,你如何排查?
- 首先确认是哪个线程消耗的CPU比较高
- 进一步排查,线程中是否有类似的"非常快速的"循环
-
确认是否这里的循环一个这么快
- 应该的话就可以升级更好的CPU
- 如果不应该,说明需要在循环中引入一些等待操作
-
上述代码结果顺序是不确定的:
- 多线程的调度是无序的,即抢占式执行:任何一个线程 在执行任何一个代码的时候,都可能被其他线程抢占CPU资源
2.实现Runnable接口
java
package thread;
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
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) {
throw new RuntimeException(e);
}
}
}
}
-
Runnable的作用:是描述一个"任务",表示"可执行的"
-
和继承Thread类创建线程比较
- 一是Thread自己记录要干啥
- 二是Runnable记录要干啥,Thread负责执行
好处:解耦合,把任务和线程拆分开,把这样的任务给其他地方执行
3.匿名内部类
3.1 创建Thread⼦类对象
java
package thread;
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) {
throw new RuntimeException(e);
}
}
}
};
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
-
过程
- 创建一个Thread子类
- 同时创建一个该子类的实例
但是对于匿名内部类来说,只能创建这一个实例,因为拿不到名字
- 子类内部重新父类的run方法
-
好处:简单
3.2 创建Runnable⼦类对象
java
package thread;
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) {
throw new RuntimeException(e);
}
}
}
});
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
-
过程:和3.1代码一样
-
区别:此处的匿名内部类只是针对Runnable,和Thread没有关系
只是把Runnable的实例,作为参数传入Thread的构造方法中 -
Q1:为什么在main方法中处理sleep异常有两种选择,而线程run中的sleep方法只有一种
-
main方法可以
- throws
- try catch
- 线程中的run方法
- 只能try catch
- 因为父类run中没有抛出异常,由于重写要求方法签名是一样的的,也无法抛出异常
throws其实是方法签名的一部分(方法名字+方法的参数列表【不包含返回值和pubic/private】+声明抛出的异常)
-
4.lambda表达式(推荐)
java
package thread;
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(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
- 本质就是一个"匿名函数",一次性函数,用完就丢
小结:
- 5种方法都是要把线程的任务内容表示出来
- 通过Thread的start来创建/启动系统中的线程
Thread对象和操作系统中的线程是一一对应的关系
🔥面试题:Java中创建线程都有哪些写法
继承Thread类
实现Runnable接口
匿名内部类
- 创建Thread子类
- 创建Runnable子类
lambda表达式
实现Callable接口(TODO)
使用线程池(TODO)