多线程初阶(一)

目录

1.认识多线程

(1)详解多线程

(2)进程和线程的区别

2.Thread创建线程

(1)继承Thread重写run方法

(2)实现Runnable重写run

(3)匿名内部类实现继承Thread重写run

 (4)匿名内部类实现Runnable重写run

 (5)基于lambda表达式创建线程

3.Thread类及常见方法

(1)常见构造方法

(2)Thread的几个常见属性

(3)启动一个线程-start()

4.线程的核心操作

(1)创建线程start()

(2)中断线程interrupt()

(3)等待一个线程join()

(4)获取当前线程currentThread()

(5)休眠当前线程sleep()

5.线程的状态


1.认识多线程

(1)详解多线程

像我们之前写过的所有代码都是只用了一个核心,不论我们如何优化代码,最多也只能用到一个核心,即便是跑满这个核心,其他核心也是空着的;

通过写特殊的代码,把多个CPU核心,都能利用起来,这样的代码就称为并发编程"
多进程编程,就是一种典型的并发编程.

多进程编程的缺陷:当需要频繁的创建和销毁编程时,消耗的时间和空间就会明显的增加,再者有些任务场景需要"等待IO",为了让等待IO的时间能够去做⼀些其他的⼯作,也需要用到并发编程.

因此为了解决进程开发较大的问题,创建了线程,线程可以理解成,更轻量的进程.
也能解决并发编程的问题,但是创建/销毁的开销,要比进程更低.

 线程概念:一个线程就是一个"执行流".每个线程之间都可以按照顺序执行自己的代码.多个线程之间"同时"执行着多份代码.

因此,多线程的编程,就成了当下主流的并发编程方式.

多线程编程相比多进程线程的优势?

  • 创建线程比创建进程更快.
  • 销毁线程比销毁进程更快.
  • 调度线程比调度进程更快.

(2)进程和线程的区别

所谓的进程,在系统中,是通过PCB这样的结构体来描述,通过链表的形式来组织的
对于系统中,线程,同样也是通过PCB来描述的(Linux)

进程和线程的关系:
一个进程,其实是一组PCB

一个线程,是一个PCB
一个进程包含了多个线程,此时每个线程,都可以独立的到CPU上调度执行.

线程是系统,"调度执行"的基本单位.
进程是系统"资源分配"的基本单位.

可执行程序在操作系统中的基本运行过程
一个可执行程序,运行的时候(双击)操作系统就会创建进程,给这个程序分配各种系统资源(CPU,内存,硬盘,网络带宽..)同时也会在这个进程中,创建出一个或者多个线程.这些线程再去CPU上调度执行.


我们上一篇文章所说的进程调度实际上就是线程调度,只不过时相对于一个线程的进程来说的,如果有多个线程在一个进程中,每个线程,都会有自己的状态,优先级,上下文,记账信息,每个都会各自独立的在CPU上调度执行.

进程和线程的区别

  1. 进程是包含线程的.每个进程⾄少有⼀个线程存在,即主线程。
  2. 同一个进程中的这些线程共用同一份系统资源
  3. 线程比进程,更轻量,创建线程,省去了“"分配资源"过程.销毁线程,也省去了"释放资源"过程.
    当你创建第一个线程后,该线程就会负责分配资源,当再创建线程时就不必再重新分配资源了
  4. 多个线程在一个进程中,每个线程,都会有自己的状态,优先级,上下文,记账信息,每个都会各自独立的在CPU上调度执行.

  5. 多个线程之间,可能会相互影响.线程安全问题.一个线程抛出异常,也可能会把其他线程也起带走.
    多个进程之间,一般不会相互影响.一个进程崩溃了,不会影响到其他进程.(这一点也称为“进程的隔离性")

线程数目越多越好吗?怎样进一步提高效率?

  • 线程的数目越来越多时效率就不会得到明显的提高,提高效率的关键是充分利用多核心来进行并行执行,但当线程数超出CPU的核心数目时,就无法在微观上完成所有线程的"并行"执行,就会存在严重的"竞争"

    多个线程之间,可能会相互影响.线程安全问题.一个线程抛出异常,也可能会把其他线程也起带走.
     
  • 我们可以通过升级核心数,换一个核心数更多的CPU来提高

2.Thread创建线程

线程是操作系统的概念,操作系统提供了一些API可以操作线程,不同的系统API也不同,Java对系统API进行了封装,使用Thread类就可以创建出一个线程. 

(1)继承Thread重写run方法


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

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();//回调
        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

为什么Thread这个类不需要进行导包?

在java.lang包下的所有类不需要进行导包操作,Thread,String...都不需要导包

代码说明:

  • 继承的目的是为了重写该类下的run方法
  • 重写run方法中的语句就是要即将创建出的线程,所执行的逻辑
  • t.start就是创建线程,调用start就会在进程内部,创建出一个新的线程.新的线程就会执行刚才run里面的代码
    run方法没有手动调用就被执行了,类似这种情况实际上是被系统/库/框架进行调用了,此时这种方法就被称为回调函数
  • 该代码运行起来就是一个进程,进程中包含了两个线程,main方法的线程就是主线程,一个进程至少有一个线程指的就是主线程,t.start就相当于新创建了一个线程,主线程和新线程就会并发的在CPU上执行   
    run()方法可以进行单独调用,但此时就不算是创建新的线程了
  • Thread.sleep()表示休眠时间,单位是毫秒,该方法是Thread类下的静态方法,所以直接使用类名调用即可
    静态方法可以理解为类方法,它是在编译过程中确定的
    动态方法可以理解为实例方法,他是在运行过程中确定的

     
  • 为什么run()方法里使用sleep方法时不能向上抛异常?
    此处是重写父类的run
    父类的run没有throws这样的异常声明子类,重写的时候也就不能增加throws
  • 多个线程之间,谁先去CPU上调度执行,这个过程是"不确定的"(不是数学意义的随机)而是这个调度顺序,取决于操作系统,内核里,"调度器"的实现~~
    调度器里,有一套规则,但是咱们作为应用程序开发,没法进行干预,也感受不到

此时我们可以借助第三方工具jconsole来查看线程的情况,该工具在jdk目录下的bin文件夹下

注意:要在代码运行的情况下打开,不能停止       若没有信息可尝试使用管理员身份打开

一个Java中不止有两个线程

main函数就是主线程,Thread-0就是t.start创建的新线程(代码中自己创建的线程命名的规律就是Thread-数字)

作用:

这些线程都是起到了一些辅助作用

  1. 垃圾回收
  2. 统计信息,调试信息

(2)实现Runnable重写run

package thread;

class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("hello run");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        MyRunnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

 代码说明:

  • Runnable就是用来描述要执行的任务是什么
    通过Thread创建线程,线程要执行的任务,是通过Runnable来描述的,而不是通过Thread自己来描述的
  • 只有当runnable传到Thread中,runnable才算是一个线程否则就是一个类

好处:使用Runnable这种方法的是更有利于解耦合

(3)匿名内部类实现继承Thread重写run

package thread;

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t.start();
        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

代码说明:

  • 定义匿名内部类,这个类是 Thread的子类.
  • 类的内部,重写了父类的run方法.
  • 创建了一个子类的实例.并且把实例的引用赋值给了t .

 好处:内聚性更好一些

 (4)匿名内部类实现Runnable重写run

通过匿名内部类来实现,本质就是方法二

package thread;

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello run");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t = new Thread(runnable);
        t.start();
        while(true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}





//更为简便的方法
package thread;

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello run");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
        while(true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

 (5)基于lambda表达式创建线程

package thread;

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() ->{
            while(true) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

3.Thread类及常见方法

(1)常见构造方法

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
  • 给线程起名字,不起名字默认就是Thread-0,Thread-1
    给线程起名字,不会影响到线程的执行效果.但是,起一个合适的名字,有利于调试程序.
  • ThreadGroup 线程组.
    把多个线程,放到一组里.方便统一的设置线程的一些属性.现在用的更多的是
     

(2)Thread的几个常见属性

package thread;

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() ->{
            while(true) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        System.out.println("线程ID:" + t.getId());
        System.out.println("线程Name:" + t.getName());
        System.out.println("线程状态:" + t.getState());
        System.out.println("线程优先级:" + t.getPriority());
    }
}

注意:可能大家的线程状态会出现RUNNABLE的状态, 是因为Thread-0和main是两个线程,这两个线程是并发执行的,当先执行main线程的打印就是RUNNABLE状态,先执行Thread-0的sleep就是TIMED_WATTING状态

常见属性详解:

 

  • ID:ID是JVM自动分配的,不能手动设置,是Thread对象的身份标识
  • 通常情况下,一个Thread 对象,就是对应到系统内部的一个线程(PCB)但是也可能会存在一个情况, Thread对象存在,但是系统内部的线程已经没了/还没创建,也就是new操作执行完创建对象后,但在系统中还没有实际的线程,必须在t.start执行后才会在系统中创建线程
     
  • State状态:是JVM自动分配的,不能手动设置,状态表示了当前线程处于什么状态(阻塞/就绪,Java中实则更为详细)
     
  • Priority优先级:设置不同的优先级,会影响到系统的调度.这里的影响,是基于"统计"规则的影响.直接肉眼观察,很难观察到效果
  • 辨析前台线程和后台线程

    后台线程/守护线程:在执行过程中不能阻止进程结束(虽然线程在运行,但当进程结束后线程也会随之带走)
    我们在main线程中t.start执行创建的线程Thread默认是前台线程
    • 如何将线程设置为后台(守护)线程?
      package thread;
      
      public class Demo8 {
          public static void main(String[] args) throws InterruptedException {
              //Thread后台线程
              Thread t = new Thread(() ->{
                  while(true) {
                      System.out.println("hello Thread");
                      try {
                          Thread.sleep(1000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              });
              t.setDaemon(true);//设置为后台线程,默认是前台线程
              t.start();//启动Thread线程
      
      
              //main线程中的前台线程,当前台线程结束后后台线程也会被带走
              for (int i = 0; i < 3; i++) {
                  System.out.println("hello main");
                  Thread.sleep(2000);
              }
      
          }
      }

      使用的注意事项

    • setDaemon方法默认是前台线程,只有当需要设置为后台线程时才需要使用该方法
    • setDaemon方法必须在线程启动(即调用start()方法)之前调用。否则,会抛出IllegalThreadStateException异常。
    • 一旦线程开始运行,就无法更改其守护线程的状态。
    • 在守护线程中执行的操作应该尽快完成,因为当所有非守护线程结束时,守护线程会被立即终止,而不会执行完后台线程方法中的剩余任务。
      当main前台线程执行完后,Thread线程也会被随之带走,两个线程是并行执行的,不能确定谁先执行谁后执行
       
  • 前台线程:在执行过程中能阻止进程结束
    前台线程(例如main主线程,Thread线程)能阻止进程结束,后台线程不能阻止进程结束
     

isAlive是否存活:用来判断内核线程是否存在,true表示内核的线程存在,false表示内核的线程不存在

代码中,创建的new Thread对象,生命周期,和内核中实际的线程是不一定一样的.
可能会出现,Thread对象仍然存在,但是内核中的线程不存在了这样的情况.(但是不会出现 Thread对象不存在,线程还存在这种)

1)调用start之前,内核中,还没创建线程
2)线程的run 执行完毕了,内核的线程就无了.但是Thread 对象,仍然存在.

此时我们就可以通过isAlive来区分

线程的执行顺序是未知的,当两个线程的sleep时间相同时,执行完sleep后不知道接下来两个线程谁先执行谁后执行

package thread;

public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() ->{
            for (int i = 0; i < 3; i++) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        System.out.println(t.isAlive());//false
        t.start();
        //此处Thread线程启动后不知道先执行main线程中的isAlive语句还是Thread中的打印语句
        //当Thread线程先执行则main线程中的isAlive语句就是true
        //当Thread线程后执行则main线程中的isAlive语句就是false
        System.out.println(t.isAlive());
        Thread.sleep(3000);
        System.out.println(t.isAlive());
    }
}

代码解释:

此处Thread线程启动后(t.start)不知道先执行main线程中的isAlive语句还是Thread中的打印语句
当Thread线程先执行则main线程中的isAlive语句就是true
当Thread线程后执行则main线程中的isAlive语句就是false

那为什么执行起来一直是true呢?
上述所说的不一定,不是指,双方概率均等.
实际上这里的两种情况的概率,会随着你系统的不同,随着你代码运行环境的不同,都可能存在差异 

(3)启动一个线程-start()

调用start方法,才真的在操作系统的底层创建出一个线程.

在Thread类下只有当t.start执行后才算是创建了线程,然后才会去执行run

main方法作为程序的入口不需要用start()方法启动线程,它会自动运行在一个由JVM创建的主线程中,而你自己创建的线程(Thread)则需要通过调用start()方法来启动。

4.线程的核心操作

(1)创建线程start()

经典面试题之start和run的区别?

  • start的本质是调用系统的API,在系统内核中创建线程(创建PCB,加入链表)

    start是调用系统函数,真正在系统内核中创建线程(创建PCB添加到链表中),此处的start会调用不同系统的API(Windows,Linux...不同的系统执行的系统函数是不一样的)
    当执行start创建线程之后再去执行run
  • run描述了线程要执行的任务,也可称为线程的入口

start的执行速度非常快,当start执行完毕后新线程就会开始执行,调用start的线程main线程也会继续执行,即两个线程并发执行

调用start,不一定非得是main线程调用.任何的线程都可以创建其他线程.

但一个Thread对象一次只能调用一次start即一个对象只能对应系统中一个线程,此时是因为第一次调用start时Thread的状态是NEW,调用后状态就会发生改变,只有状态是NEW时才能成功调用

package thread;

public class Demo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("t");
            Thread t2 = new Thread(() -> {
                System.out.println("t2");
            });
            t2.start();
        });
        t.start();
    }
}

(2)中断线程interrupt()

B正在运行,A想让B结束
其实核心就是A要想办法让B的run方法执行完毕,此时B就自然结束了.而不是B的run 执行一半,A直接把B强制结束了.这样做是为了确保B执行完,避免是一个半成品,这里是让B更加快速的执行完

方法一:

package thread;

public class Demo11 {
    private static boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!isQuit) {
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(2000);
        System.out.println("main线程尝试中断t线程");
        isQuit = true;
    }
}

为什么将isQuit放到main中就会出现报错呢?

变量捕获问题,isQuit和lambda定义在一个作用域中,此时lambda 内部,是可以访问到lambda外部(和lambda同一个作用域)中的变量的

观察报错信息:

  • final:一个变量被声明为final意味着它的值在初始化之后就不能再被修改。
  • effectively final:即便你没有显式地将一个变量声明为final,只要它在初始化之后没有被重新赋值,Java编译器也会将其视为effectively final。这意味着你可以在不显式使用final关键字的情况下在lambda表达式中使用它。

因此我们要写成成员变量的方式,此时进行的就是内部类访问外部类(内部类本身就能访问外部类成员)

lambda表达式,本质上是一个"函数式接口"产生的“匿名内部类"

方法二:

package thread;

public class Demo12 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            Thread currentThread = Thread.currentThread();
            while (!currentThread.isInterrupted()){
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        Thread.sleep(2000);
        t.interrupt();
    }
}

 代码解释:

  • Thread currentThread = Thread.currentThread();
    currentThread()是Thread类的静态方法.就能获取到调用这个方法的线程的实例.
    哪个线程调用,返回的引用就指向哪个线程的实例.(类似于this)
  • currentThread.isInterrupted()
    isInterrupted()是Thread类里的一个成员,它的属性是boolean,初始情况下这个变量是false,未被终止,一旦外面的其他线程,调用一个interrupt方法,就会设置上述标志位.

    这里的currentThread不能改为t?
    因为这里的执行顺序是先创建了一个匿名内部类,再执行内部类里的语句时,t线程还没有创建,也就是说下列的Lambda的代码在编译器眼里,出现在Thread t 的上方的.出时 t还沿有被定义的
  • t.interrupt();
    在主线程中控制t线程被终止,也就是设置标识位

我们发现当我们运行的时候会抛出RuntimeException异常信息

原因:

由于isInterrupt()判断语句和打印语句执行速度很快,因此整个循环基本上都在执行sleep(1000)当main线程调用interrupt时,大概率t线程正处于休眠状态
 

此处Interrupt不仅仅能设置标志位,还能把刚才这个sleep 操作,给唤醒
比如, sleep 此时刚睡了100ms,还剩900ms,此时Interrupt被调用了
此时sleep就会直接被唤醒,并且抛出InterruptedException异常

由于catch 中默认代码再次抛出异常,再次抛出的异常
没人catch,最终就到了JVM这一层,进程就直接异常终止了.
 

当我们将catch语句改成简单的sout打印语句时发现虽然不抛出异常了但并没有进行中断操作,标志位好像没设置一样,一直在执行,明明此时的interrupt把sleep唤醒了,异常也被catch住了,那原因是什么呢?

首先标志位肯定是设置了的只是sleep等阻塞的函数被唤醒之后,就会先清空刚才设置的interrupted标志位,因此想要结束线程就需要在catch中return/break

这里我们通过一个简单的例子来理解这种中断操作

Java中,终止线程,是一个"温柔"的过程,不是强行就终止了

我和女朋友正在看电视,突然女朋友跟我说她渴了让我去帮他买瓶水,此时我可以做出三种选择:

  1. 无视女朋友,继续看电视

    B线程直接无视A线程,catch中什么也不做,B线程就会继续运行,对应代码就是sleep清楚标志位使B继续执行,如果sleep不清楚标志位的话,B就会结束,无法继续执行代码
  2. 停止看电视,立即去买水

    如果B线程想立即结束,就直接在catch 中写上return 或者break.此时,B线程就会立即结束.
     
  3. 看完这一集再去买水

    如果B想稍后再结束,就可以在catch中写上一些其他的逻辑(比如释放资源,清理一些数据,提交一些结果..…收尾工作)这些逻辑完成之后,再进行return / break;
     

Java中,终止线程,是一个"温柔"的过程,不是强行就终止了

A希望B线程终止,B收到这样的请求之后,B需要自行决定,是否要终止/立即还是稍后(B线程内部的代码来决定的,其他线程无权干涉 

(3)等待一个线程join()

线程等待:在操作系统中针对多个线程的执行,是一个随即调度,抢占式执行的过程


线程等待,就是在确定两个线程的"结束顺序"

无法确定两个线程调度执行的顺序,但是可以控制,谁先结束,谁后结束.
让后结束的线程,等待先结束的线程即可.
此时后结束的线程就会进入阻塞,一直到先结束的线程,真的结束了,阻塞才解除

t等main:
public class Demo14 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("t开始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t结束");
        });

        t.start();
        System.out.println("main开始");
        try {
            t.join(); //main等待t    t执行完再执行main
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main结束");

    }
}




main等t:
在执行主线程的内容时,加上等待时间,确保t线程执行完毕

此时join并没有发生阻塞,t线程已经结束了
join就是确保,被等待的线程,能够先结束.
如果已经结束了, join就不必再等了

public class Demo15 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("t开始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t结束");
        });

        t.start();

        Thread.sleep(4000);
        //此处时间必须大于for循环整体的时间,这样才能保证t线程执行完再执行main线程

        System.out.println("main开始");
        try {
            t.join(); //main等待t    t执行完再执行main
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main结束");

    }
}



t2等t1:
必须在t2执行前来设置t.join()
a线程中调用b.join就是a等待b结束

public class Demo17 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                System.out.println("t1开始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t1结束");
        });
        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 3; i++) {
                System.out.println("t2开始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t2结束");
        });
        System.out.println("main开始");

        t1.start();
        t2.start();
    }
}

以上操作中使用到的join操作都是无参的版本,无参数,意思是"死等"
被等待的线程,只要不执行完,这里的等待,就会持续阻塞
 

(4)获取当前线程currentThread()



实现t线程等待main线程

//t线程等待主线程
public class Demo18 {
    public static void main(String[] args) {
        Thread mainthread = Thread.currentThread();//获取main线程
        Thread t = new Thread(() -> {
            try {
                mainthread.join();//t等待main
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 3; i++) {
                System.out.println("t开始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t结束");
        });
        System.out.println("main开始");
        t.start();
        System.out.println("main结束");
    }
}

(5)休眠当前线程sleep()

Thread.sleep让调用的线程阻塞等待是有一定时间的,当线程执行sleep时就会使这个线程不参与cpu的调度,从而把cpu的资源让出来给别人使用,也叫“放权”操作
某个线程cpu占用率过高时就可以通过sleep来进行改善

5.线程的状态

  1. NEW(初始状态):当前Thread对象虽然有了,但是内核的线程还没有(还没调用过start)
  2. TERMINATED(终止状态):当前Thread对象虽然还在,但是内核的线程已经销毁(线程已经结束了)
  3. RUNNABLE(就绪状态):正在cpu 上运行+随时可以去cpu上运行
  4. WAITING(等待状态):没有超时时间的阻塞等待,join / wait
  5. TIME_WAITING(超时等待) :有超时时间的等待,比如sleep,或者join带参数版本.....
  6. BLOCKED(阻塞状态):由于某种原因导致正在运行的线程放弃了CPU资源并且暂停了执行,此时线程进入了阻塞状态。
public class Demo19 {
    public static void main(String[] args) throws InterruptedException {
        //WAITING
        Thread mainThread = Thread.currentThread();
        Thread t = new Thread(() -> {
            while (true){
                try {
                    System.out.println(mainThread.getState());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t.start();
        t.join();
        //System.out.println(t.getState());
    }
    public static void main4(String[] args) throws InterruptedException {
        //TIMED_WAITING
        Thread t = new Thread(() -> {
            while (true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t.start();
        Thread.sleep(500);
        System.out.println(t.getState());
    }
    public static void main3(String[] args) throws InterruptedException {
        //RUNNABLE
        Thread t = new Thread(() -> {
            while (true){

            }
        });

        t.start();
        System.out.println(t.getState());
    }

    public static void main2(String[] args) throws InterruptedException {
        //TERMINATED
        Thread t = new Thread(() -> {
        });

        t.start();
        Thread.sleep(1000);
        System.out.println(t.getState());
    }

    public static void main1(String[] args) {
        //New
        Thread t = new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                System.out.println("t开始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t结束");
        });
        System.out.println(t.getState());
        t.start();
    }
}

 

 

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值