多线程编程及Thread类

一、多线程编程

4.1 主线程

public class Demo1 {

    public  static void main(String[] args) {
        System.out.println("hello world");
    }
}

在这段代码中也是涉及到了线程的,当我们运行这个程序的时候,操作系统会创建一个Java进程,而我们知道一个进程里至少包含一个线程,在Java进程里就会有一个线程调用main方法,这个调用main方法的线程叫做主线程

4.2 创建线程

在Java中创建线程,离不开Thread类,Thread类是标准库里提供的
创建线程的方法有很多,下面一一介绍

方法一:继承Thread类
(1)创建MyThread类继承Thread类,重写run方法

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}

重写run方法的目的是,明确创建出的新线程要执行的任务

(2)创建MyThread实例并调用start方法启动线程

 public  static void main(String[] args) {
        Thread t=new MyThread();
        t.start();
        System.out.println("hello main");
    }

只有执行了t.start()才算是真正的创建出了线程(在操作系统内核中创建出对应线程的PCB,然后将PCB加入到就绪队列中,参与调度)

执行结果:
在这里插入图片描述
细心的同学可能发现在代码中我们是先调用的start,后打印的hello main,但执行结果却是先打印的hello main
原因如下:
1.每个线程是独立的执行流
2.main对应的线程是一个执行流,MyThread是另一个执行流,这两个执行流之间的关系是并发执行的关系
3.两个线程的执行顺序取决于操作系统调度器的具体实现,我们这里可以简单的视为"随机调度"

也就是说,hello main 和hello thread 的打印顺序不是确定的

【补充】:

在这里插入图片描述代表进程执行结束,在OS中用进程的退出码来表示进程的运行结果0:进程执行完毕,结果正确非0:进程执行完毕,结果不正确如果进程崩溃,可能会返回一个随机值
上面进程在一瞬间就结束了,我们使用死循环的方法不让进程结束

【jconcole工具的使用】

class MyThread extends Thread{

    @Override
    public void run() {
        while(true){
            System.out.println("hello thread");
        }
    }
}
public class Demo1 {

    public  static void main(String[] args) {
        Thread t=new MyThread();
        t.start();
        while (true){
            System.out.println("hello main");
        }
    }
}

这样进程就会一直执行,此时我们就可以查看当前进程里有哪些线程
在这里插入图片描述
在任务管理器中,我们能看到java进程,但要想看java进程中还有哪些线程,需要借助JDK提供的jconcole工具
(1)
在这里插入图片描述
(2)选择进程
在这里插入图片描述
(3)
在这里插入图片描述
(4)
在这里插入图片描述
在这里插入图片描述
【代码改进】:
细心的同学可能会发现,上述的代码打印的太快了,不方便我们观察,于是我们可以使用sleep函数来让线程适当的"休息"一下, 指定让线程摸一会🐟,不要上CPU干活,sleep()是Thread类的静态成员方法,参数的单位是ms

1ms=1000us;1us=1000ns;1ns=1000ps;
计算机中,进行一次网络通信,花的时间大概是us~ms级的
进行一次读写硬盘,花的时间大概是ns~ms级的
进行一次读写内存,花的时间大概是ps~ns级的

【InterruptedException异常分析】

在这里插入图片描述

在代码中加上sleep后,会报这InterrupetedException异常, sleep(1000),就是要休眠1000ms,但是在休眠的过程中,可能会有意外情况打断它的休眠状态,提前唤醒了,这是就会抛出这个异常

【改进后的代码】:


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 Test1{
    public  static void main(String[] args) {
        Thread t=new MyThread();
        t.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    } } 
    执行结果:
    在加了sleep函数之后,无序调度这个事情就能被更清楚的观察到了	

在这里插入图片描述

【经典面试题】: Thread中的run方法和start方法的区别

作用功能不同:

  1. run方法的作用是描述线程具体要执行的任务;
  2. start方法的作用是真正的去申请系统线程

运行结果不同:

  1. run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
    在这里插入图片描述
    在这里插入图片描述
  2. start调用方法后, start方法内部会调用Java 本地方法(封装了对系统底层的调用)真正的启动线程,并执行run方法中的代码,run 方法执行完成后线程进入销毁阶段。

方法二:实现Runnable接口

(1)实现Runnable接口并重写run方法

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

(2)创建Thread类实例,调用Thread的构造方法时将Runnable对象作为target参数

在这里插入图片描述

public class Test2 {
    public static void main(String[] args) {
     //此处创建的Runnable,相当于是定义了一个"任务",
     //还是需要Thread实例,把任务交给Thread
     //通过Thread.start方法来创建具体的线程
        Runnable runnable=new MyRunnable();
        Thread thread=new Thread(runnable);
        thread.start();
    }
}

【区别】:

方法一中,是将线程和任务内容绑定在一起的
方法二中,线程和任务是分离开的,这样能够好的解耦合(模块/代码之间,关联关系越紧密耦合性越高,反之,耦合性越低)
假设这个任务不想通过多线程来执行了,而是换成别的方式执行,代码改动很小
低耦合的好处是,一个模块出现问题,对另一模块的应先不大
继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()

【问题】:

在上面的代码中,我们使用到了很多的类,Thread,Runnable,InterruptedException,为什么都不需要inmport呢?以及什么样的类不需要import呢?

在这里插入图片描述
答案:这些类都在java.lang包中,不需要import
还有处在同一个包中的类也不需要import

方法三:匿名内部类创建 Thread 子类对象

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

在这里插入图片描述

方法四:匿名内部类创建Runnable子类对象

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

在这里插入图片描述

方法五:lambda表达式,来定义任务(推荐用法)

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

形如,lambda表达式这样的,能够简化代码编写的语法规则,称为"语法糖" 。还有for-each,var等。

除了上面的5中方式,还有Callable/FutureTask的方式,以及线程池的创建方式,学完回来补充。

4.3 多线程的优势

可以观察多线程在一些场合下是可以提高程序的整体运行效率的。

  • 更充分的利用CPU的多核资源
package thread;

public class Test6 {
    private static final long count=2000000000;
    //serial 串行的完成两次20亿的自增操作
    private static void serial(){
        //开始时间
        //使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.
        long begin=System.currentTimeMillis();
        int a=0;
        for (long i = 0; i < count; i++) {
            a++;
        }
        a=0;
        for (long i = 0; i < count; i++) {
            a++;
        }
        //结束时间
        long end=System.currentTimeMillis();
        System.out.println("单线程消耗的时间:"+(end-begin)+"ms");
    }

     private static void concurrency(){
         long begin=System.currentTimeMillis();
        Thread t1=new Thread(()->{
            int a=0;
            for (long i = 0; i <count ; i++) {
                a++;
            }
        });
        Thread t2=new Thread(()->{
            int a=0;
            for (long i = 0; i <count ; i++) {
                a++;
            }
        });
        t1.start();
        t2.start();
         long end=System.currentTimeMillis();
         System.out.println("并发执行消耗的时间:"+(end-begin)+"ms");
     }

    public static void main(String[] args) {
            serial();
            concurrency();
    }
    //执行结果:
    单线程消耗的时间:1004ms
    并发执行消耗的时间:33ms
}

这里的concurrency方法是有问题的
在这里插入图片描述
正确的concurrency方法:

package thread;

public class Test6 {
    private static final long count=2000000000;
    //serial 串行的完成两次20亿的自增操作
    private static void serial(){
        //开始时间
        long begin=System.currentTimeMillis();
        int a=0;
        for (long i = 0; i < count; i++) {
            a++;
        }
        a=0;
        for (long i = 0; i < count; i++) {
            a++;
        }
        //结束时间
        long end=System.currentTimeMillis();
        System.out.println("单线程消耗的时间:"+(end-begin)+"ms");
    }

     private static void concurrency() throws InterruptedException {
         long begin=System.currentTimeMillis();
        Thread t1=new Thread(()->{
            int a=0;
            for (long i = 0; i <count ; i++) {
                a++;
            }
        });
        Thread t2=new Thread(()->{
            int a=0;
            for (long i = 0; i <count ; i++) {
                a++;
            }
        });
        t1.start();
        t2.start();
         t1.join();
         t2.join();
         long end=System.currentTimeMillis();
         System.out.println("并发执行消耗的时间:"+(end-begin)+"ms");
     }

    public static void main(String[] args) throws InterruptedException {
            serial();
            concurrency();
    }
}
//执行结果:
单线程消耗的时间:977ms
并发执行消耗的时间:489ms

在这里插入图片描述
我们看到串行和并行所消耗的时间并不是正好的二倍关系,这是因为创建线程自身也是有开销的,而且两个线程在CPU上并不一定是纯并行的,也有可能是并发的,线程的调度也是开销的

4.4 多线程的使用场景

  1. CPU密集型场景
    代码大部分工作,都是在使用CPU进行运算(就向上面的频繁自增运算的过程) 使用多线程可以更好的利用CPU多核计算资源,从而提高效率

  2. IO密集型场景
    读写硬盘、读写网卡…这些多是IO操作(IO操作是很消耗时间的),IO的过程大部分是不消耗CPU的, 这就说明CPU会摸鱼,就可以给CPU安排些任务,避免CPU过于闲置。例如,在去食堂打饭,排队等待的过程(类似于等待IO结束),我们的大脑(类似于CPU)是闲置的,于是我们就可以掏出书来看书(一边等待,一边读书)

4.5 多线程在系统中的调度

在这里插入图片描述
【注意】:
这这段代码中虽然调用start函数的顺序是t1,t2,t3
但真正执行的顺序不一定是按照t1,t2,t3的顺序执行
调用start函数只是将线程PCB挂到就绪队列中
何时执行还要看调度器的调度

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

但如果像下面这样:
大概率会是t1先在系统中执行了

  public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            System.out.println("hello t1");
        });
        Thread t2=new Thread(()->{
            System.out.println("hello t2");
        });
        Thread t3=new Thread(()->{
            System.out.println("hello t3");
        });
        t1.start();
        Thread.sleep(1000);
        t2.start();
        t3.start();
    }

二、Thread类及常见方法

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
每个线程都是一个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
在这里插入图片描述

2.1 Thread的常见构造方法

在这里插入图片描述
【注意】:

在这里插入图片描述
这两种方法是可以给线程起名字的,线程在操作系统内核中是没有名字的,只有一个身份标识,但是在Java中,为了让程序员调试的时候方便理解这个线程是谁,就在JVM里给对应的Thread对象加了个名字
这个名字对程序的执行没有影响,但对程序员来说还比较有用 但如果不手动设置,也会有默认的名字 形如Thread-0,Thread-1…

这个构造方法的演示示例
在这里插入图片描述

package thread;

public class Test2 {
    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) {
                        e.printStackTrace();
                    }
                }
            }
        },"新线程");
        thread.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

我们可以在jconsole中观察线程:

在这里插入图片描述
在这里插入图片描述

2.2 Thread的常见属性

在这里插入图片描述

【说明】:

  • getId()这个方法获取到的是线程在JVM中的身份标识
  • 线程的身份标识有好几个,内核的PCB上有标识,在用户态线程库里,也有标识(pthread,操作系统提供的线程库),JVM中也有标识(JVM Thread类底层也是调用操作系统的pthread库),虽然三个标识各不相同,但目的都是一样的,都是作为身份的区分,类似于每个人都有他的大名,小名,外号一样
  • 在这里,我们只关心JVM中的身份标识就可以了
  • 后台线程也叫做"守护线程",类似于我们打开一个手机app后,app就来到了"前台",之后我们在回到主界面,此时app就切换到了"后台",线程也是分为前台线程和后台线程的(是可以设置的)
  • 设置后台线程需要在启动之前设置(start之前),启动之后就不能设置了
  • 一个线程创建出来默认是前台线程,前台线程会阻止线程结束,进程会保证所有的前台线程都执行完了,进程才会退出,后台线程不会阻止进程结束
  • main线程是前台线程
    在这里插入图片描述
    对于上面这个代码来说,由于thread是前台线程,进程要等到它执行完才退出
    在这里插入图片描述
    当我们将该线程设置为后台线程(通过setDaemon方法)后,进程一下就执行完了
  • ID 是线程的唯一标识,不同线程不会重复
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的一个情况,下面我们会进一步说明
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
  • 是否存活,即简单的理解,为 run 方法是否运行结束了
  • 线程的中断问题,下面我们进一步说明

【代码演示】:

package thread;

public class Test2 {
    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) {
                        e.printStackTrace();
                    }
                }
            }
        },"新线程");
        thread.start();
        System.out.println(thread.getId());
        System.out.println(thread.getName());
        System.out.println(thread.getPriority());
        System.out.println(thread.getState());
        System.out.println(thread.isAlive());
        System.out.println(thread.isDaemon());
        System.out.println(thread.isInterrupted());
    }
}
//执行结果:
20
新线程
5
TIMED_WAITING
true
false
false
上述操作获取的都是一瞬间的状态,而不是持续的状态

2.3 启动一个线程-start()

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。调用 start 方法, 才真的在操作系统的底层创建出一个线程.

  • 覆写 run 方法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是把 李四叫过来了
  • 而调用 start() 方法,就是喊一声:”行动起来!“,线程才真正独立去执行了

线程执行结束:只要让线程的入口方法(Thread的run方法,或者Runnable或者lambda里的执行任务)执行完了,线程就随之结束了
主线程的入口方法就可以视为是main方法

2.4 中断一个线程

一旦进到工作状态,李四就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那该如通知李四停止呢?这就涉及到我们的停止线程的方式了。
所谓的中断线程,就是让线程尽快的把入口方法执行结束
常见的中断线程的方式有以下两种方式:

  1. 通过共享的标记来进行沟通

【代码示例】:

package thread;

public class Test3 {
    //用一个布尔变量表示线程是否要结束
    //这个变量是成员变量
    private static boolean isQuit=false;

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
           while (!isQuit){
               System.out.println("线程运行中....");
               try{
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            System.out.println("新线程执行结束");
        });
        t.start();
        Thread.sleep(5000);
        System.out.println("控制新线程退出");
        isQuit=true;
    }
}
//执行结果:
线程运行中....
线程运行中....
线程运行中....
线程运行中....
线程运行中....
控制新线程退出
新线程执行结束
Process finished with exit code 0

在上面的新线程中,控制这个新线程的退出,主要是破坏循环条件

  1. 调用 interrupt() 方法来通知

使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted()
代替自定义标志位.Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.
currentThread()是Thread类中的静态方法,调用Thread.currentThread()的作用是获取当前线程的实例,也就是说线程1在调用这个方法就返回线程1的Thread对象,和我们之前学的this很像

在这里插入图片描述
在这里插入图片描述
【代码示例】:

package thread;

public class Test4 {
    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();
                }
            }
            System.out.println("新线程执行结束");
        });
        t.start();
        Thread.sleep(5000);
        t.interrupt();
    }
}

执行结果:
在这里插入图片描述
我们发现这个代码在执行5s后会触发一个InterruptedException异常,但虽然报出了异常,打印完异常后线程还会继续执行,这是为什么呢?

这里就需要我们注意理解interrupt方法的行为:

(1) 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常(例如上面的代码是因为调用sleep而阻塞,所以interrupt方法就会让sleep抛出InterruptedException异常)的形式通知,清除中断标志
(调用interrupt方法后就会设置标志位,发现线程阻塞后,会让导致线程阻塞的方法抛出异常,之后清除标志位)

  • 当出现 InterruptedException 的时候,由于我们在代码中使用了try-catch进行捕获, 要不要结束线程取决于 catch 中代码的写法. 可以选择 忽略这个异常, 也可以跳出循环结束线程,也就是说程序员可以自行控制线程的退出行为,这也做到了主线程发出"退出"命令的时候,新线程自己来决定,如何处理这个退出的行为

(2)如果调用该方法的线程没有处在阻塞状态,此时interrupt就会修改内置标志位

【注意】:

主线程调用interrupt的时候,新线程执行到哪个部分,是阻塞的代码还是非阻塞的代码时随机的,只不过面的代码,阻塞时间比较长,非阻塞的时间很短

① 捕获到InterruptedException异常后,立即退出

package thread;
public class Test4 {
    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) {
                    break;
                }
            }
            System.out.println("新线程执行结束");
        });
        t.start();
        Thread.sleep(5000);
        t.interrupt();
    }
}

执行结果:
在这里插入图片描述
②等一会再退出

package thread;

public class Test4 {
    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) {

                    System.out.println("线程即将退出");
                    try {
                    //这里的sleep可以换成任意用来首尾 的代码
                        Thread.sleep(3000);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    break;
                }
            }
            System.out.println("新线程执行结束");
        });
        t.start();
        Thread.sleep(5000);
        t.interrupt();
    }
}

执行结果:
在这里插入图片描述
③不退出,忽略这个异常

package thread;

public class Test4 {
    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) {

                }
            }
            System.out.println("新线程执行结束");
        });
        t.start();
        Thread.sleep(5000);
        t.interrupt();
    }
}

执行结果:
在这里插入图片描述
【Thread.interrupted()和Thread.currentThread().isInterrupted()】的区别:

  • interrupted()是Thread类中的静态方法
  • isinterrupted()是普通的成员方法
  • Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志(控制线程中断,这里的标志位会设置为true,读取的时候会读取到这个true,读完之后标志位自动恢复成false)
  • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志(状态不会自动恢复)

【小结】:
在Java中,线程中断的设定是 ,将决定权交给要被结束的线程自身了,Java中为了避免任务执行一半就退出的情况,就将决定权转移了
在操作系统原生的线程库,中断的时候,决定权是调用者(调用中断的人),线程会被立即中断

【代码示例】:

package thread;

public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("线程运行中....");
            }
            System.out.println("新线程执行结束");
        });
        t.start();
        Thread.sleep(5000);
        System.out.println("控制新线程退出");
        t.interrupt();
    }
}

如果我们将新线程中的sleep去掉后,当主线程中打印完"控制新线程退出"后,新线程就一定立即退出吗?
不一定,多线程的随即调度的情况是非常复杂的,线程1执行的任意代码之间(准确说是任意指令之间)都可能会插入其他的线程2的若干个指令,对于上面这段代码来说就是在 System.out.println(“控制新线程退出”);t.interrupt();这两个代码之间,可能会插入新线程的指令

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

2.5 线程等待-join

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。虽然我们不能控制两个线程的开始执行顺序,但是可以通过join来控制两个线程的结束顺序
在这里插入图片描述

在这里插入图片描述
【通过join实现main等待t2,t2等待t1】

package thread;

public class Test2 {
    private static  Thread t1=null;
    private static Thread t2=null;
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main begin");
        t1=new Thread(()->{
            System.out.println("t1 begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 end");
        });
       t1.start();
        t2=new Thread(()->{
            System.out.println("t2 begin");
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2 end");
        });
        t2.start();
        t2.join();
        System.out.println("main end");
    }

}
//执行结果:
main begin
t1 begin
t2 begin
t1 end
t2 end
main end

【main先运行t1,t1执行完再执行t2】

package thread;
public class Test1{
    private static  Thread t1=null;
    private static Thread t2=null;
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main begin");
        t1=new Thread(()->{
            System.out.println("t1 begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 end");
        });
        t1.start();
        t1.join();
        t2=new Thread(()->{
            System.out.println("t2 begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2 end");
        });
        t2.start();
        t2.join();
        System.out.println("main end");
    }
}
//执行结果:
main begin
t1 begin
t1 end
t2 begin
t2 end
main end

【小结】:

join的行为:如果被等待的线程还没执行完,就阻塞等待;如果被阻塞的线程已经执行完了,就直接返回

2.6 获取当前线程的引用

在这里插入图片描述

package thread;

public class Demo1 {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}
//执行结果:
main

【注意】:

如果是继承Thread类,然后重写run方法,可以直接在run中使用this即可获取到线程的实例
如果是Runnable或者lambda,this就不行了,因为此时this指向的是Runnable实例或函数接口的实例

2.7 休眠线程

在这里插入图片描述
sleep的作用效果:
在这里插入图片描述

【注意】

在某个线程中调用了sleep(1000),那么1000ms之后该线程一定会上CPU运行吗?
不一定,1000ms之后只是将该线程的PCB放回了就绪队列,至于说该线程什么时候能上CPU执行,还要看调度器的调度
因此程序sleep(1000),意味着休眠时间不大于1000,实际的休眠时间会略大于1000,误差在10ms以下

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值