1.认识线程(Thread)

1.1概念

进程是系统分配资源的最小单位,线程时系统调度的(cpu执行指令)的最小单位。一个进程内的线程之间是可以共享资源的。每个进程至少有一个线程的存在,这个线程就被称为主线程(指的是C语言的main函数,而非java main函数)。

1.2接触观察进程和线程

线程可以通过JDK提供的java监控工具或命令来观察。

运行方式:双击,或者在cmd中输入命令

线程监控子线程 主线程如何监控线程_主线程


运行测试程序1,并观察线程,main发生阻塞

public class first {
    //运行一个进程,main就是主线程
    public static void main(String[] args) throws InterruptedException {
        //第一段代码:观察main阻塞
        Thread.sleep(9999999L);       
}
}

结果:

线程监控子线程 主线程如何监控线程_子线程_02


java进程的运行过程

本质上是调用java包名.类名来启动java程序,类名作为参数传递==>java进程运行。

java命令代码执行:

最先启动系统main方法,一般是C语言的main入口函数。----这个是系统级别的main线程,是系统级别的主线程。

执行过程图示:

线程监控子线程 主线程如何监控线程_多线程_03

运行测试程序2,观察线程。子线程阻塞

//第二段代码:观察子线程阻塞
  public class first {
    //运行一个进程,main就是主线程
    public static void main(String[] args) throws InterruptedException {
        //匿名内部类,相当于一个子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
    try{
 //在这行代码中发生阻塞,程序就在这里停止住了
    Thread.sleep(9999999L);
    }catch (InterruptedException e){
       e.printStackTrace();
                }
            }
        },"first").start();
    }
   }

结果:

线程监控子线程 主线程如何监控线程_子线程_04

运行测试程序3,观察线程。main线程和子线程都阻塞

//第三段代码:观察main和子线程同时阻塞
 public class first {
    //运行一个进程,main就是主线程
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
       try{
     //在这行代码中发生阻塞,程序就在这里停止住了
       Thread.sleep(9999999L);
       }catch (InterruptedException e){
        e.printStackTrace();
        }
      }
        },"first").start();
        Thread.sleep(9999999L);
    }
    }

结果:

线程监控子线程 主线程如何监控线程_多线程_05


所以可以看到:java main线程是可以没有的,前面说的每个进程都至少有一个主线程,是指系统级别的主线程也就是C语言的main函数。

1.3多线程的执行方式

线程监控子线程 主线程如何监控线程_线程监控子线程_06


面试:run()和start()的区别

  1. Thread一个线程(创建状态)。
  2. start()启动线程,申请系统调度并运行(就绪态)。
  3. run()系统调度线程时,线程处于运行态时,会执行run()里面的代码(运行态)。
  4. 有就绪态什么时候转换为运行态,是由系统调度自身决定的。

例:main线程直接电调用run()方法

线程监控子线程 主线程如何监控线程_主线程_07


分析:这里没有start()方法启动线程,这里的t.run()只是main线程的对象方法调用,没有启动线程,所以在监控器里自然只有main线程阻塞。例:main线程和子线程同时运行,观察执行顺序

线程监控子线程 主线程如何监控线程_子线程_08


结果:

线程监控子线程 主线程如何监控线程_线程监控子线程_09


分析:(面试时,要保证说服严谨)

运行过程:new Thread创建一个子线程,当执行到start()时,主线程会申请系统调度执行子线程,但此时的主线程仍处于自身的运行状态,所以会继续向下执行自身的打印代码行。当主线程的时间片用完,系统电镀轮转到子线程时,子线程处于运行态,执行run()方法里的打印代码。

运行结果:从概率上说,main字符串先打印的概率比较大。如果start()调用以后,main的时间片刚好用完,此时系统调度刚好切换到子线程,并且子线程很快调度并执行,三者都满足此时才先打印出“first”。所以在概率上说,“main线程”会先被打印出来。

1.4多线程的使用场景(优劣势)

1.什么时候使用多线程?
(1)同时执行多个任务,当每个任务量比较大的时候,可以使用多线程提高效率。
(2)当前线程执行阻塞式代码时,需要同时执行其他代码,可以使用多线程达到不阻塞执行其他代码的目的。
2.多线程的效率考虑两个因素:这两个因素都与系统资源有关
(1)单个线程的任务量——任务量越小,相对创建线程及申请系统调度的时间性能来说,就不划算。
(2)运行的线程数量——系统资源,包括cpu核数,cpu频率,内存大小等都相关。在当前系统可用资源的条件下,多线程数量有一定的阈值。

  • 如果单个任务量比较多,使用多线程可以提高效率,用增加线程数来提高效率,但当达到阈值后,效率就开始下降了。

1.5多线程的执行顺序分析

例1:

线程监控子线程 主线程如何监控线程_主线程_10


例2:

线程监控子线程 主线程如何监控线程_主线程_11


结果:随机打印出来这5个线程的当前名字

线程监控子线程 主线程如何监控线程_线程监控子线程_12

2.线程创建

2.1线程创建的方法

方法1:继承Thread类
该方法的好处是this代表当前线程,不需要通过Thread.currentThread()来获取当前线程的引用。

//创建线程的方法1:继承一个Thread类
//好处:this就是当前线程,不需要通过Thread.currentThread()来获取当前线程的引用
public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(this.getName());
    }
    public static void main(String[] args){
        new MyThread().start();//线程开始运行
    }
}

方法2.实现Runnable接口
通过实现Runnable接口,并且调用Thread的构造方法时,将Runnable对象作为参数传入用来创建线程对象。该方法的好处是:可以规避类的单继承的限制,但是需要通过Thread.currentThread()来获取当前线程的引用。

//创建线程的方式2
//创建线程的方式一定是Thread,Runnable只是一个参数接口
//好处:避免类的单继承限制,需要通过Thread.currentThread()来获取当前线程的引用
public class MyRunnable  implements Runnable{
    @Override
    public void run() {
         System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args){
        //使用Runnable对象创建线程对象,Runnable只是任务描述
       Thread t = new Thread(new MyRunnable());
       t.start();//线程启动
    }
}

方法3:使用匿名内部类创建Thread的子类对象

new Thread(new Runnable() {
            @Override
            public void run() {
                    System.out.println("first");
            }
        },"first").start();

2.2Thread类以及常用方法

Thread类是JVM用来管理线程的一个类,每个线程都有唯一的一个Thread对象与之关联。
Thread对象就是用来描述一个线程执行的流的,JVM将这些Thread对象组织起来,用于线程调度,线程管理。

当前线程:静态方法被调用的代码行所在线程为当前线程。

Thread的常见构造方法:

线程监控子线程 主线程如何监控线程_主线程_13


Thread的几个常见属性:

线程监控子线程 主线程如何监控线程_线程监控子线程_14


Thread的几种静态方法:

线程监控子线程 主线程如何监控线程_子线程_15


Thread的几种实例方法:

线程监控子线程 主线程如何监控线程_多线程_16

各属性方法操作:

public class ThreadDome {
    public static void main(String[] args) {
         Thread t = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "我还活着");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "我即将死去");

        });
        System.out.println(Thread.currentThread().getName() + "名称:" +t.getName());
        System.out.println(Thread.currentThread().getName() + "状态:" + t.getState());
    }

}

join()方法操作:

线程监控子线程 主线程如何监控线程_主线程_17


体会在多线程中使用join()方法:

//同时运行多线程,等待所有线程执行完毕,再执行main后面的方法
public class MultiThreadFinishThenDoMain {
    
    public static void main(String[] args) throws InterruptedException {
        //目前的代码:是mian先打印,然后0-19随机同时打印
        //预期结果:先随机同时打印0-19,全部打印完之后,再打印main
        //不能在循环内部加join,如果在循环里面加,就达不到同时执行20个子线程的目的
        // 先在外面获取这20个线程的引用对象,然后再调用阻塞
        //通过数组或者集合的方法,来获取这20个线程的引用对象,再取出来再主线程中join阻塞,就能达到目的了
        Thread[] threads = new Thread[20];
        for (int i = 0;i < 20;i++){
            final int j = i;
           Thread t = new Thread(()->{
                    System.out.println(j);
            });
            t.start();
            threads[i] = t;
        }
        for (int i = 0;i < 20;i++){
            //子线程同时运行的,这里的join只是对主线程有阻塞影响
            threads[i].join();
        }
        System.out.println("main");
    }
}

线程监控子线程 主线程如何监控线程_线程监控子线程_18

中断一个线程

线程监控子线程 主线程如何监控线程_线程监控子线程_19


根据代码来观察上三种中断方法的区别:

代码1:

线程监控子线程 主线程如何监控线程_线程监控子线程_20


代码2:

线程监控子线程 主线程如何监控线程_线程监控子线程_21


代码3:

线程监控子线程 主线程如何监控线程_多线程_22


代码4:

线程监控子线程 主线程如何监控线程_子线程_23


中断的三种方法总结(常考):

线程监控子线程 主线程如何监控线程_主线程_24