JavaEE初阶--------第三章 Thread类的基本用法

系列文章目录

JavaEE初阶--------第三章 Thread类的基本用法


前言

前面两章我们主要了解了什么是进程,什么是线程以及二者之间的一个区别。那么,到底该怎样去创建一个线程呢?这是我们这章主要要掌握的内容。


一、线程创建

使用 Thread 类,创建 Thread 对象,创建出一个线程出来。

方法一:继承 Thread 类

先上代码

class MyThread extends Thread{
    @Override
    public void run() {
        //这个方法是线程的入口方法
        while (true){
            System.out.println("hello thread!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        //start 和 run 都是 Thread 的成员
        //run 只是描述了线程的入口(线程要做什么任务)
        //start 则是真正调用了系统api,在系统中创建出线程,让线程再调用 run
        t.start();
        while (true){
            System.out.println("hello main!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述
上面代码的运行结果就是hello main!和hello thread!一直死循环打印下去。可以看到,这两个 while 循环在“同时执行”,看到的结果,是两边的日志都在交替打印的效果,这也就实现了并发编程的效果

  • 首先要创建一个类来继承 Thread 类,紧接着是重写 run 方法,run 方法里面就是我们需要线程来执行的任务。接下来,在 main 方法中首先要 new 出一个实例对象 t ,然后,调用start 方法启动线程 t 。后面,就是主线程的代码了,执行主线程的任务。
  • 这两线程都是休眠1000ms,当时间到了之后,这两线程谁先执行,谁后执行,就不一定了。这个过程可以视为是“随机”的。操作系统对于多个线程的调度顺序是不确定的,是“随机的”。

方法二:实现 Runnable 接口

class MyRunnable implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("hello Runnable!");
            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 thread = new Thread(runnable);
        thread.start();
        while (true){
            System.out.println("hello main!");
            Thread.sleep(1000);
        }
    }
}

在这里插入图片描述

  • 实现 Runnable 接口,重写 run 方法
  • Runnable 表示的是一个“可以执行的任务”,这个任务是交给线程负责执行,还是交给其他的实体来执行,Runnable 本身并不关心。所以,还是要搭配 Thread 的构造方法来实现多线程。
  • 使用 Runnable 的写法和直接继承 Thread 之间的区别,主要就是解耦合。(简单来说就是,减少模块之间的依赖性,提高程序的独立性)
  • 创建一个线程,需要进行两个关键操作:1、明确线程要执行的任务。2、调用系统api 创建出线程
方法三、使用匿名内部类(更简洁)
public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while (true){
                    System.out.println("hello thread!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        thread.start();
        while (true){
            System.out.println("hello main!");
            Thread.sleep(1000);
        }
    }
}
public class Demo4 {
    public static void main(String[] args) {
        Thread thread = 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);
                   }
               }
            }
        });

        thread.start();

        while (true){
            System.out.println("hello main!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 上面这两种就是匿名内部类了,先创建出新的类,这个类的名字是啥不重要,因为这个类本身就是只用一次,同时又把这个子类的实例给创建出来。这里是子类,可以重写父类的方法
  • 使用匿名内部类的方式本质上和上面两种是一样的,只是会更加简洁一些罢了。
方法四:基于lambda 表达式(推荐)
public class Demo5 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {                     //lambda表达式
            while (true){
                System.out.println("hello thread!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        thread.start();
        while (true){
            System.out.println("hello main!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

在这里插入图片描述

  • 更简化的语法表达方式,“语法糖”,相当于是“匿名内部类”的替换写法。lambda表达式,本质上是一个匿名函数,主要是用来实现“回调函数”的效果。
  • 回调函数,不是你主动调用的,也不是现在就立即调用,而是在后面合适的时机来调用这个函数。

二、线程终止

在Java中,要销毁/终止一个线程,做法比较唯一,就是想办法让run 方法尽快执行结束

  • 方案一
    可以在代码中手动创建出标志位,来作为 run 的执行结束的条件。很多线程执行的时间久,往往是因为这里写了一个持续执行的循环,要想让 run 执行结束,就是要让循环尽快退出。
public class Demo6 {
    private static boolean isQuit = false;     //手动创建标志位
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() ->{
           while (!isQuit){
               System.out.println("线程工作中!");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            System.out.println("线程工作完毕!");
        });

        thread.start();
        Thread.sleep(5000);
        isQuit = true;
        System.out.println("设置isQuit为ture");
    }
}

在这里插入图片描述
这里,我们就自己手动创建了一个标志位,当我们需要让线程终止时,就设置一下标志位,就可以将线程终止,

  • 方案二(推荐)
    Thread 类内部,有一个现成的标志位,可以用来判定当前的循环是否要结束。
public class Demo7 {
    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,让线程立即结束
               }

           }
        });

        t.start();
        Thread.sleep(5000);
        System.out.println("让t 线程终止!");
        t.interrupt();

    }
}

在这里插入图片描述

  • 首先获取到当前线程的实例,此处得到的就是 t ,Thread 内部有一个标志位,然后调用 isInterrupted( ) 方法就可以用来判断对象关联的线程的标志位是否设置,调用后不清楚标志位。

  • 调用 t.interrupt( ) 方法,把上述 Thread 对象内部的标志位设置为 true 了,这样就可以使线程终止了。

  • 即使线程内部的逻辑出现阻塞(sleep),也是可以使用这个方法来唤醒的。正常来说,sleep 会休眠到时间到为止才能唤醒,此处给出的 interrupt 就可以使 sleep 内部触发一个异常,从而提前被唤醒,方案一中手动定义标志位,是无法实现这个效果的。

  • 注意interrupt 唤醒线程之后,此时 sleep 方法会抛出异常,但同时会自动清除刚才设置的标志位。这样,并不能达到终止线程的效果(如下图)。所以,要在 while 循环内加上 break ,来结束循环。
    在这里插入图片描述

三、线程等待

  • 让一个线程等待另一个线程执行结束,再继续执行,本质上就是控制线程结束的顺序。
public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() ->{
            for (int i = 0; i < 5; i++) {
                System.out.println("t 线程工作中!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t.start();

        System.out.println("join 开始等待!");
        t.join();
        System.out.println("join 结束等待!");
    }
}

在这里插入图片描述

  • 从上述运行结果可以看出,主线程调用了 t.join( ) 方法之后,主线程就不再往下运行了,而是去运行 t 线程中的代码,当 t 线程执行完毕以后,再接着回来执行主线程接下来的代码
  • t.join 工作过程:
    1、如果 t 线程正在运行中,此时调用 join 的线程就会阻塞,一直阻塞到 t 线程执行结束为止
    2、如果 t 线程已经执行结束了,此时调用 join 线程,就直接返回了,不会涉及到阻塞。
  • 一般来说,等待操作都是带有一个“超时时间”的。
public void join(long millis)

等待线程结束,最多等 millis 毫秒。

四、线程休眠

public static void sleep(long millis)throws InterruptedException
  • Thread.sleep() 方法调用之后,系统会唤醒线程,线程由阻塞状态转变成就绪状态,这样线程也就可以到CPU 上继续运行了
  • 有一点要记住,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。

五、获取线程实例

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

           }
        });
}        

这里的 Thread.currentThread( ) 就是获取到当前线程的实例,也就是 Theread t

  • 哪个线程调用这个方法就会返回哪个线程的对象。

总结

这一章,我们主要总结了 Thread 类的基本用法,如何去创建一个线程,大致有三种方法(1.继承 Thread,重写run;2.实现 Runnable ,重写run;以及上述两种方法的匿名内部类 3.使用 lambda 表达式)。怎样去终止一个线程如何让一个线程等待另一个线程,还有如何休眠一个线程,还有就是获取线程实例。通过这章的学习,我们就可以去完成一些线程的基本操作了、

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值