目录
[2.2.实现 Runnable 接口](#2.2.实现 Runnable 接口)
[2.3.匿名内部类创建 Thread ⼦类对象](#2.3.匿名内部类创建 Thread ⼦类对象)
[2.4.匿名内部类创建 Runnable ⼦类对象](#2.4.匿名内部类创建 Runnable ⼦类对象)
[2.5.lambda 表达式创建 Runnable 子类对象(最常用,后续创建我都会用这种方法)](#2.5.lambda 表达式创建 Runnable 子类对象(最常用,后续创建我都会用这种方法))
[3.2.调用interrupt() 方法来通知](#3.2.调用interrupt() 方法来通知)
一.什么是Thread类
java.lang.Thread 类是 Java 实现多线程编程的基础核心类,用于创建、控制和管理线程。运用时无需手动导包。
二.线程创建
start()方法--启动一个线程
作用:启动一个新线程,JVM自动调用run方法
注意:start()方法仅允许调用一次,多次调用会报异常
创建一个 Thread 对象,并不代表进程中就存在了对应的执行线程;只有调用 start() 方法,才会真正在进程中创建并启动线程。
2.1.继承Thread类
通过自定义一个类继承Thread类,并重写他的run()方法
java
//继承 Thread, 重写 run
class myThread extends Thread{
public void run(){
while(true){
System.out.println("my thread");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException{
Thread t=new myThread();
t.start();
while(true){
System.out.println("my main");
Thread.sleep(1000);
}
}
}
2.2.实现 Runnable 接口


可以看到Thread类本身实现了Runnable接口,同时其构造方法支持接收Runnable 接口的实现类。所以我们先定义一个类实现Runnable接口

并重写 run() 方法,再将该实现类的实例传入 Thread类的构造方法来创建线程。
java
class myRunnable implements Runnable{
public void run(){
while(true){
System.out.println("my thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo2 {
public static void main(String[] args) throws InterruptedException{
Runnable runnable=new myRunnable();
Thread t=new Thread(runnable);
t.start();
while(true){
System.out.println("main");
Thread.sleep(1000);
}
}
}
2.3.匿名内部类创建 Thread ⼦类对象
java
//继承 Thread, 重写 run, 使用匿名内部类
public class Demo3 {
public static void main(String[] args) throws InterruptedException{
Thread t=new Thread(){
public void run(){
while(true){
System.out.println("my thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
while(true) {
System.out.println("my main");
Thread.sleep(1000);
}
}
}
2.4.匿名内部类创建 Runnable ⼦类对象
java
//实现 Runnable, 重写 run, 使用匿名内部类
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable=new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("my thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t=new Thread(runnable);
t.start();
while(true){
System.out.println("main");
Thread.sleep(1000);
}
}
}
2.5.lambda 表达式创建 Runnable 子类对象(最常用,后续创建我都会用这种方法)
java
//使用 lambda 表达式
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(true) {
System.out.println("my thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
while(true){
System.out.println("main");
Thread.sleep(1000);
}
}
}
三.线程中止
3.1.通过共享的标记来进行沟通
首先我们来看一个写法
java
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
boolean isFinished=false;
Thread t1=new Thread(()->
{
while(!isFinished){
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1线程结束");
});
t1.start();
//保证t1线程启动一段时间后
Thread.sleep(3000);
isFinished=true;
System.out.println("main线程结束");
}
}

这段看似正确的代码却报错了,那么问题出在哪,为什么会报错呢?
java
Thread t1=new Thread(()->
{
while(!isFinished){
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1线程结束");
});
我们知道这是一个Lambda表达式,主线程先执行完创建线程的逻辑,未来由 JVM 线程调度器自动调用 这个任务里的代码, 这种先定义 ,后被别人调用 的函数**,**就是回调函数。
我们看到这个Lambda内部使用了外部方法的变量,这个过程就叫变量捕获,Java对捕获变量是有强制要求的,他要求Lambda捕获的变量必须是**有效不可变的(被定义为final,或者一直不改变),**因为变量捕获是值捕获,捕获的是变量当前值,而不是变量本身,如果变量发生改变,不会影响到捕获的值,就会产生诡异的局面,两个同名变量但是值不同。
java
public class Demo4 {
static boolean isFinished=false;
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->
{
while(!isFinished){
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1线程结束");
});
t1.start();
//保证t1线程启动一段时间后
Thread.sleep(3000);
isFinished=true;
System.out.println("main线程结束");
}
}
如果我们将变量定义成一个全局的变量,每次拿到他都是拿的变量本身
为了避免自己创建的标记产生上面的问题,我们可以用到Java自带的现成变量。
3.2.调用interrupt() 方法来通知
我们知道Lambda定义是在创建现成之前,也就是在类对象引用声明之前,故而无法通过类对象的引用直接来调用isInterrupted()方法来判断线程是否被终止,所以我们需要借用Thread的另一个方法

我们无法看到这个方法是内部如何写的,由native修饰,底层为CPP代码实现,但是我们知道她的的用处就是:在哪个线程被调用,返回的就是哪个线程的Thread引用。返回true就是已经被标记为中断状态。

interrupt()的方法是主动进行终止
java
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->
{
while(!Thread.currentThread().isInterrupted()){
System.out.println("thread");
}
System.out.println("t1线程结束");
});
t1.start();
//保证t1线程启动一段时间后
Thread.sleep(1);
t1.interrupt();
System.out.println("main线程结束");
}
}

可能有人会疑惑,诶?为什么这个创建的新线程不加上sleep方法了,这就是我要讲的interrupt()方法的另外一个作用,唤醒像sleep()这样的阻塞方法(强制唤醒),强制唤醒就会导致异常。
java
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->
{
while(!Thread.currentThread().isInterrupted()){
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
System.out.println("t1线程结束");
});
t1.start();
//保证t1线程启动一段时间后
Thread.sleep(3000);
t1.interrupt();
System.out.println("main线程结束");
}
}

当前线程直接终止,后续线程代码不会执行
java
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->
{
while(!Thread.currentThread().isInterrupted()){
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
System.out.println("t1线程结束");
});
t1.start();
//保证t1线程启动一段时间后
Thread.sleep(3000);
t1.interrupt();
System.out.println("main线程结束");
}
}
但是如果我们在sleep()强行唤醒后捕捉到异常后break终止循环,那么还是可以继续执行捕捉到异常之后的代码,但是如果我们在捕获异常中什么都不做,又会产生什么现象呢
java
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->
{
while(!Thread.currentThread().isInterrupted()){
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//throw new RuntimeException();
//break;
}
}
System.out.println("t1线程结束");
});
t1.start();
//保证t1线程启动一段时间后
Thread.sleep(3000);
t1.interrupt();
System.out.println("main线程结束");
}
}

按理说,我们调用了interrupt()方法,线程已经终止了,为什么运行的结果和我们所想是完全不同的呢?这是因为sleep()方法被强制唤醒后,又会把isinterrupted()方法内部的已经修改成true的标志位,又重新修改成false,所以循环还是会继续进行。
三.线程等待
有时,我们需要等待一个线程完成后,才进行自己的下一步工作
java
public class Demo3 {
static int count=0;
public static void main(String[] args) {
Thread t1=new Thread(()->
{
for (int i = 0; i < 100; i++) {
count++;
}
});
t1.start();
System.out.println("count="+count);
}
}

可以看到这个代码的运行结果是count=0,为什么呢?这是因为主线程提前运行结束了,可能t1线程还没启动,主线程就已经运行了打印count大小的代码,所以为了防止这种情况发生,我们需要让主线程等待t1线程运行完毕后再执行打印count变量的代码,于是join()方法就可以运用在这里
java
package Thread;
public class Demo3 {
static int count=0;
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->
{
for (int i = 0; i < 100; i++) {
count++;
}
});
t1.start();
t1.join();
System.out.println("count="+count);
}
}

当然join()方法的括号中还可以加上参数,意思是等这么就,要是超过了,就不再等待
四.线程休眠
线程休眠的方法有两种:
- sleep(long millis) 休眠当前的线程,休眠的时间单位是毫秒
- sleep(long millis,int nanos) 休眠当前的线程,休眠单位是毫秒和纳秒
可能会被强制唤醒,所以要提前抛异常,或者用try...cache...捕获异常
代码调用sleep()方法,相当于当前线程让出CPU资源,后续时间到了的时候,需要操作系统内核把这个线程重新调用回CPU上,才能继续被执行,时间到了之后,意味着允许被调用,而不是立即执行,sleep(0)意味着线程自动放弃CPU资源,等待操作系统的重新调度。
五.获取线程实例
我们在'调用interrupt() 方法来通知',这一节已经详细解释过了,就是调用Thread类的静态方法currentThread()方法来获取当前线程的实例。
Thread 类的基本用法我们差不多讲解完毕了,想知道线程的更多知识,请听下回分解,下回我们会讲解线程的几种状态