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

1. 线程的创建

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

1.1 继承 Thread 类

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

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

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 接口

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

        【JavaEE初阶二】 Thread类及常见方法_第1张图片

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

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

代码讲解如下图所示:

【JavaEE初阶二】 Thread类及常见方法_第2张图片

3、调用 start 方法

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

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

  代码如下:

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

【JavaEE初阶二】 Thread类及常见方法_第3张图片

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

        代码如下:

 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一个合法的解释

        代码如下:

 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();
                }
            }
        });

【JavaEE初阶二】 Thread类及常见方法_第4张图片

 2. Thread 类及常见方法

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

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

                 【JavaEE初阶二】 Thread类及常见方法_第5张图片

2.1 Thread 的常见构造方法

【JavaEE初阶二】 Thread类及常见方法_第6张图片

        大体使用如下:

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

        命名操作:

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

        代码如下:

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();
    }
}

        结果如下:

【JavaEE初阶二】 Thread类及常见方法_第7张图片

2.2 Thread 的几个常见属性

【JavaEE初阶二】 Thread类及常见方法_第8张图片

常见属性说明:

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

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

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

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

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

        代码举例:

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());
    }
}

        结果展示:

【JavaEE初阶二】 Thread类及常见方法_第9张图片

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已经结束了,结果如下:

【JavaEE初阶二】 Thread类及常见方法_第10张图片

        观察线程的状态:

【JavaEE初阶二】 Thread类及常见方法_第11张图片

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

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

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

【JavaEE初阶二】 Thread类及常见方法_第12张图片

【JavaEE初阶二】 Thread类及常见方法_第13张图片

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

【JavaEE初阶二】 Thread类及常见方法_第14张图片

 2.3 启动一个线程-start()

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

【JavaEE初阶二】 Thread类及常见方法_第15张图片

        运行结果如下所示:

【JavaEE初阶二】 Thread类及常见方法_第16张图片

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

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

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

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、详细分析如下图图解所示:

【JavaEE初阶二】 Thread类及常见方法_第17张图片

2.4  中断一个线程

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

2.4.1 引入标志位

        代码如下:

【JavaEE初阶二】 Thread类及常见方法_第18张图片

        测试结果:

【JavaEE初阶二】 Thread类及常见方法_第19张图片

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

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

2.4.2 标志位优化

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

【JavaEE初阶二】 Thread类及常见方法_第20张图片

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

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

【JavaEE初阶二】 Thread类及常见方法_第21张图片

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

【JavaEE初阶二】 Thread类及常见方法_第22张图片 代码如下所示:

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方法之后,关于谁等待谁的问题?具体图解如下图所示:

【JavaEE初阶二】 Thread类及常见方法_第23张图片

        深入分析:

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

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

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

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

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

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

你可能感兴趣的:(java,java-ee)