【JavaEE初阶二】 Thread类及常见方法

1. 线程的创建

主要来简单学习一下以下几种方法:

1.1 继承 Thread 类

具体代码见前面的一章节,主体的步骤有以下几部分;

1、继承 Thread 来创建一个自定义线程类MyThread

java 复制代码
class MyThread2 extends Thread{
    //重写run方法
    @Override
    public void run() {
        //run 方法就是该线程的入口方法
        while (true){
            System.out.println("hello thread,委婉待续");

            try {
                Thread.sleep(1000);//休眠1000ms
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

2、创建 MyThread 类的实例

3、调用 start 方法启动线程

1.2 实现 Runnable 接口

1、实现 Runnable 接口

java 复制代码
class MyThread3 implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("hello runnable");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

runnable 可以理解成"可执行的",通过该接口,就可以抽象的表示出一端可以被其他实体来执行的代码;同时也可以简单的理解成runnable指的是重写的run方法的代码块;

2、创建 Thread 类实例, 且调用 Thread 的构造方法时将 Runnable 对象作为目标传递参数

代码讲解如下图所示:

3、调用 start 方法

总体代码如下:继承Thread,重写run,但是使用匿名内部类

java 复制代码
package thread;

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

public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread3());
        t.start();

        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

1.3 使用匿名内部类

内部类,在一个类里面定义的类~~匿名内部类最大的用途,没有名字意味着只能使用一次就无法生效了

1.3.1 匿名内部类创建 Thread 子类对象

继承Thread,重写run

代码如下:

java 复制代码
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
	@Override
	public void run() {
		System.out.println("使用匿名类创建 Thread 子类对象");
	}
};

1.3.2 匿名内部类实现runnable,重写run

代码如下:

java 复制代码
 public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("使用匿名类创建 Runnable 子类对象");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();

总体来说,()里面的内容是thread父类构造方法的参数,代码部分其实是填写了runnable的匿名内部类的实例

1.3.3 lambda 表达式创建 Runnable 子类对象

(函数式接口属于lambda背后的实现),相当于java在没被破坏原有的规则(方法不能脱离类,而单独存在)基础上,给了lambda一个合法的解释

代码如下:

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

2. Thread 类及常见方法

Thread 类是 JVM 用来管理线程的一个类,即每个线程都有一个唯一的 Thread 对象与之关联。

每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象 就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

2.1 Thread 的常见构造方法

大体使用如下:

java 复制代码
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是当前线程的名字");
Thread t4 = new Thread(new MyRunnable(), "这是当前线程的名字");

命名操作:

可以通过使用 jconsole 命令观察线程时的看到自己的重命名线程名子

代码如下:

java 复制代码
package thread;

public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread,it's smallye");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "委婉待续001");

        // 在 start 之前, 设置线程为 后台线程 (不能在 start 之后设置)
       // t.setDaemon(true);

        t.start();
    }
}

结果如下:

2.2 Thread 的几个常见属性

常见属性说明:

1、ID 是线程的唯一标识,不同线程不会重复; Getid()是jvm自动分配的身份标识,会保证线程的唯一性

2、名称是各种调试工具用到

3、状态表示线程当前所处的一个情况;Getstate():进程有状态(就绪,阻塞),线程也有状态,java中对线程的状态,有进行了进一步的区分;

4、优先级高的线程理论上来说更容易被调度到

5、关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行;Isdaemon(demon:守护)判断是否是守护线程,也叫做"是否是后台线程"; 前台线程的运行会阻止进程结束,后台进程的运行不会阻止进程结束;

代码举例:

java 复制代码
public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ": 我还活着");
                    Thread.sleep(1 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 我即将死去");
        },"委婉待续001");
        System.out.println(Thread.currentThread().getName()
                + ": ID: " + thread.getId());
        System.out.println(Thread.currentThread().getName()
                + ": 名称: " + thread.getName());
        System.out.println(Thread.currentThread().getName()
                + ": 状态: " + thread.getState());
        System.out.println(Thread.currentThread().getName()
                + ": 优先级: " + thread.getPriority());
        System.out.println(Thread.currentThread().getName()
                + ": 后台线程: " + thread.isDaemon());
        System.out.println(Thread.currentThread().getName()
                + ": 活着: " + thread.isAlive());
        System.out.println(Thread.currentThread().getName()
                + ": 被中断: " + thread.isInterrupted());
        thread.start();
        while (thread.isAlive()) {}
        System.out.println(Thread.currentThread().getName()
                + ": 状态: " + thread.getState());
    }
}

结果展示:

java 复制代码
package thread;

public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread,it's smallye");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "委婉待续001");
        // 在 start 之前, 设置线程为 后台线程 (不能在 start 之后设置)
       // t.setDaemon(true);

        t.start();
    }
}

如上代码执行的时候,t持续执行,但是main已经结束了,结果如下:

观察线程的状态:

在idea中。没有出现下图情况时,我们的进程不算结束;

咱们代码创建的t线程,默认就是前台线程,会阻止进程结束,只要前台线程没执行完,进程就不会结束,即时main已经执行完毕

如下面的代码,我们将我们的线程设置为后台线程,他不会阻止我们的线程结束,故此有如下结果;

6、是否存活,即简单的理解,为 run 方法是否运行结束了;IsAliva:表示了内核中的线程pcb是否存在,Java代码中定义的线程对象(thread)实例虽然表示一个线程,但是对象本身的生命周期和内核中的pcb的生命周期是不一样的;

2.3 启动一个线程-start()

Thread类,使用一个start方法,来启动一个线程,对于同一个thread对象来说,只能调用一次。下图中的一个thread类对象被start启动两次,会有异常;

运行结果如下所示:

此时可以看到,另外一个线程依旧在运行 ;IlllegalThreadStateException,该异常是指非法的线程状态异常;即想要启动更多的线程,就是得创建新的对象。

调用strart创建出新的线程(本质上是start会调用系统的api,来完成创建线程的操作)

Q:下面的代码中关于start和run的区别,以及两者之间的关系?

java 复制代码
package thread;

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

public class ThreadDemo11 {
    public static void main(String[] args) {
        Thread t = new MyThread4();
        t.start();
        // t.run();

        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

A: 1、在上述代码中,两个词语没有一点关系;

2、详细分析如下图图解所示:

2.4 中断一个线程

中断一个线程(终止一个线程)-->让线程run方法(入口方法)执行完毕---->核心就是让run方法能够提前结束----->非常取决于具体代码实现方式了

2.4.1 引入标志位

代码如下:

测试结果:

通过上述代码引入标志位isquit,就可以让t线程结束掉,具体线程结束的时候却取决于在另外一个线程中何时修改isquit的值。

Main线程要想让t线程结束,其前提是t线程的代码,对于这样的逻辑有所支持,而不是t里的代码随便咋写都能够提前结束的。

2.4.2 标志位优化

上述方法是手动设置变量,来进行结束线程。我们的优化是尝试让一个thread类,进行内置变量;代码解说图解如下:

我们的线程中,如果没有sleep,interrupt可以让线程如我们所料的那样顺利结束,但是sleep引起了变数。刚才在执行sleep的过程中,调用interrupt,会大概率出现sleep休眠时间还没到,就被提前唤醒了。我们的线程被提前唤醒,会做以下两件事:

  1. 抛出interruptException(紧接着就会被catch获取到)
  2. 清除thread对象的isInterrupted标志位(通过interrupt方法,已经把标志位设置为true了,但是sleep提前唤醒,就把标志位又设回到false,所以此时循环还是会继续执行),如下情况:

所以如果依旧想让线程按照原先的计划结束,我们需要在catch语句中添加break语句;

代码如下所示:

java 复制代码
package thread;

public class ThreadDemo13 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("我是一个线程, 正在工作中...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // e.printStackTrace();
                    // 加上 break , 此时抛出异常之后, 线程也会结束
                    break;
                }
            }
            System.out.println("线程执行完毕!");
        });

        t.start();

        Thread.sleep(3000);
        // 使用一个 interrupt 方法, 来修改刚才标志位的值
        System.out.println("让 t 线程结束");
        t.interrupt();
    }
}

2.5 join()-等待一个线程

多个线程的执行顺序是不确定的,虽然线程的底层的调度是无序的,但是我们可以在应用程序中。通过一些api来影响线程执行的顺序;所以我们引入了join方法,该方法能够应该线程执行的先后顺序;

比如t1线程等待t0线程,则最后的结果是t0线程结束之后t1线程才会被执行;join会导致等待的那个线程处于阻塞的状态(开车如果1车被2车加塞,则2车需要等待,所以2车被阻塞)

接下来我们要知道使用join方法之后,关于谁等待谁的问题?具体图解如下图所示:

深入分析:

执行join的时候,接下来主要是看t线程是否在运行:

1、如果t在运行,main线程就会处于阻塞状态(t线程加入了,所以main线程就不去参加cpu的执行了)

2、如果t运行结束,main线程就会从阻塞中恢复过来,继续向下执行(加塞的前面的那个车走了,我的车就需要继续执行我的任务了,总之导致我的任务结束的比前面的车晚)

综上所述,是由于阻塞,是这两个线程的结束时间产生了先后关系;

ps:本次的内容就到这里了,如果大家感兴趣的话就请一键三连哦!!!

我的idea的背景图是刘姝贤,如果感兴趣的话关注b站up主刘景灵000!!!

相关推荐
记得开心一点嘛8 分钟前
Nginx与Tomcat之间的关系
java·nginx·tomcat
界面开发小八哥21 分钟前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse
王伯爵23 分钟前
<packaging>jar</packaging>和<packaging>pom</packaging>的区别
java·pycharm·jar
Q_19284999061 小时前
基于Spring Boot的个人健康管理系统
java·spring boot·后端
m0_748245172 小时前
Web第一次作业
java
小码的头发丝、2 小时前
Java进阶学习笔记|面向对象
java·笔记·学习
m0_548514772 小时前
前端Pako.js 压缩解压库 与 Java 的 zlib 压缩与解压 的互通实现
java·前端·javascript
坊钰2 小时前
【Java 数据结构】移除链表元素
java·开发语言·数据结构·学习·链表
chenziang12 小时前
leetcode hot100 LRU缓存
java·开发语言