一.创建线程

1.继承Thread类,重写run方法

class MyThread extends Thread{
    public void run(){
        for (int i = 0; i < 3; i++) {
            System.out.println("hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
   }
  }

sleep:休眠,让当前的线程暂时放弃CPU,休息一会,时间过了再执行。

 public static void main(String[] args) throws InterruptedException{
        MyThread t = new MyThread();
        t.start();
        for (int i = 0; i < 3; i++) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }

上述,代码是有两个线程,一个是t线程,main线程是入口。由于,每个线程之间是并发执行的,是无法预支结果的执行顺序的。

2.实现runable 接口,重写run方法

class MyRunnable implements Runnable{
    public void run(){
        for (int i = 0; i < 3; i++) {
            System.out.println("hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public static void main(String[] args) throws InterruptedException{
        Runnable myRunnable = new MyRunnable();
        Thread t = new Thread(myRunnable);
        t.start();
        for (int i = 0; i < 3; i++) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }

上述代码的写法,可以降低耦合度,即模块之间,依赖尽量小,影响也会小。

3.使用匿名内部类

 Thread t = new Thread(){
     public void run(){
        for (int i = 0; i < 3; i++) {
            System.out.println("hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
 }

这种情况,一般如果某个代码是“一次性”的,就可以使用匿名内部类。

Thread t = new Thread(){}
这种方式进行创建,1.创建子类,我们是不知道子类是什么名字的。
2.创建了这个匿名内部类的实例,并且把实例的引用赋值给了t.

4.使用Runable,使用匿名内部类

Runnable runnable = new Runnable(){
     public void run(){
        for (int i = 0; i < 3; i++) {
            System.out.println("hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
 }
Thread t = new Thread(runnable);
t.start();

5.lambda表达式

 Thread t = new Thread(() ->{
     public void run(){
        for (int i = 0; i < 3; i++) {
            System.out.println("hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
 });

lambda表达式 本质上是一个“匿名函数”,主要用途“回调函数”。“回调函数”:写一个函数,自己不去调用,让别人去调用。

二.Tread类及常见的方法

Thread类是JVM管理线程的一个类,即每个线程都有唯一1个Thread对象与之对应。

 下边这些呢,是一些常用的构造方法。

在这里插入图片描述

Thread(Runnable target ,String name)使用:

       Runnable myRunnable = new MyRunnable();
        Thread t = new Thread(myRunnable,"t1");

在我们没有创建名字的时候,默认是Thread-0,Thread-1 这种写法。

Thread的几个常见属性

在这里插入图片描述

1. getId()

用来表示线程身份的效果,类似于Pid

2. getName()
  用来获取当前线程的名字。
3. isDaemon()
   本身的含义:是否是守护线程
   守护线程==后台线程

什么是后台线程? 相当于是没有什么存在感
它们的存在不会影响进程的结束,即使他们还继续存在,如果进程要结束了,它们也结束了。
前台线程:这几个线程的存在,会影响到进程的继续存在,这样的线程,称为“前台线程”。
代码实例如下:

  Thread t = new Thread(() ->{
       while(true){
            System.out.println("hello Thread");
            try {
                 Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
           }
       }
  });
      
  t.start();
  for (int i =0;i<3;i++){
      System.out.println("hello main");
      Thread.sleep(1000);
   }
}

执行结果:
在这里插入图片描述

上述代码,
1.线程的执行顺序是不确定的,无法保证执行顺序。
2.我们自己创建的线程 t线程是属于前台线程,main也属于前台线程。t线程就会控制线程执行的时间,由于 t 线程一直是while(true),所以将会一直执行,直到强制结束。
3.Thread.sleep()会让当前线程暂停执行指定时间,期间不会占用 CPU 资源。

         我们想要让上述代码,在main线程结束的时候,
         就结束整个线程。我们的做法是:
         在t.start()之前设置 setDaemon
       t.setDaemon(true);
       t.start();

添加了这一行代码之后,我们就将t线程变成了后台线程,无法阻止线程的结束。当主线程for循环结束之后,JVM会查看是否还有其他用户线程还再继续,由于t线程设置为了后台线程,JVM不会等待t线程结束,而是直接终止。

4.isAlive()

Java中创建的Thread对象,和系统中的线程是一一对应的。
但是,Thread对象的生命周期,和系统中的线程生命周期,是不同的。
(可能会存在,Thread对象还存活,但是系统中的线程已经销毁了)

 Thread t = new Thread(() ->{
       for (int i =0;i<3;i++){
            System.out.println("hello Thread");
             try {
                  Thread.sleep(1000);
             } catch (InterruptedException e) {
                    throw new RuntimeException(e);
             }
        }
  });
  //没有启动线程之前,观察线程的存活状态
  System.out.println(t.isAlive());
   t.start();
    for (int i =0;i;i++) {
        System.out.println(t.isAlive());
         Thread.sleep(1000);
      }
    }

在这里插入图片描述

在上述代码中,首先,在没有启动线程的时候,存活状态是false,在启动之后,main线程执行到最后一次的时候,可能t线程还没有完全的结束,所以返回的还是true。

5.isInterrupted()

终止线程,让线程直接结束,即让线程的入口方法执行完毕,线程就随之结束了。

方式一:设置变量的方式
   private static boolean flag = false;

    public static void main(String[] args) throws InterruptedException{

        Thread t= new Thread(() ->{
               while (!flag) {
                   System.out.println("hello main");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
            System.out.println("t线程结束");
        });
        t.start();
        Thread.sleep(3000);
        flag = true;
    }

在上述代码中,

private static boolean flag = false; 设置为了静态成员变量

是通过flag变量来进行线程终止的,lamdba表达式是函数式接口,即匿名内部类,在内部类中访问外部类的静态成员变量,是可以直接进行访问的,所以,当线程休眠3秒之后,flag变为true,是可以让线程终止的。

  private static boolean flag = false;
  当flag设置为局部变量的时候,情况如下

在这里插入图片描述
当设置为局部变量的时候,我们想要在lamdba表达式里边访问flag变量,这时,就会触发“变量捕获”。
lamdba是回到函数,执行时机是很久之后,(操作系统真正创建出线程之后,才会执行的),很有可能后续线程创建好了,当前main这里的方法已经执行完毕了,对应的flag也就销毁了。

那么,为了解决上述的问题:把被捕获的变量拷贝1份,拷贝到lamdba里面。就算外部的变量销毁了,也不会影响里边的执行了。

拷贝呢就意味着 这个变量不适合进行修改。
在拷贝了一份之后,可能 lamdba表达式里边的变量和外部定义的变量就不是一回事了。所以,这样,一边改变,一边不变的,会很不好。
所以,这里就压根不能进行修改。

在这里插入图片描述
所以,flag这里的提示是:应该是final 或 实际上是final.

使用interrupt()去主动终止线程

        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    break;
                }
            }
            System.out.println("t 结束");
        });
        t.start();
        Thread.sleep(3000);
        System.out.println("main 线程尝试终止 t 线程");
        t.interrupt();
    }

在上述代码中,
Thread.currentThread() 获取当前正在执行的线程对象
Thread.currentThread().isInterrupted() 判断当前线程是否被终止。

t.interrupt() 主动去终止,除了设置标志位之外,还可以唤醒像sleep这样的阻塞方法。

t.interrupt();
调用这个interrupt 这个方法会将isInterrupted 方法内部的标志位设为true. 但是,上述代码呢,是因为提前把sleep唤醒,在sleep唤醒之后,就会把isInterrupted在此设置回false.
如果,还是要进行循环的判定,则会发现代码继续执行下去。

所以,可以针对异常处理来使循环结束。

  try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
             break;
     }

break : 就会立即终止线程
在这里插入图片描述

什么也不做处理 : 不终止
在这里插入图片描述

如果是抛出异常,在结果中就会显示异常
在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐