【扎实基本功】Java基础教程系列之多线程

1. 多线程的概念

1.1 进程、线程、多进程的概念

  • 进程:正在进行中的程序(直译)。
  • 线程是程序执行的一条路径, 一个进程中可以包含多条线程。
    • 一个应用程序可以理解成就是一个进程。
    • 多线程并发执行可以提高程序的效率, 可以同时完成多项工作。

1.2 多线程应用场景

  • VNC同时共享屏幕给多个电脑。
  • 迅雷开启多条线程一起下载。
  • QQ同时和多个人一起视频。
  • 服务器同时处理多个客户端请求。

1.3 并行和并发的区别

  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)。
  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于间时间隔较短,使人感觉两个任务都在运行(画图-任务调度)。

1.4 Java程序运行原理

  • Java命令会启动java虚拟机(JVM),等于启动了一个应用程序,也就是启动了一个进程。
  • 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法
  • ==一个应用程序有且只有一个主线程,程序员不能New主线程,可以New子线程。==
    在这里插入图片描述

1.5 JVM启动的是多线程吗?

  • JVM启动至少启动了垃圾回收线程主线程,所以是多线程的。
  • main方法的代码执行的位置就是在主线程(路径)。
  • 一个进程有多个线程。
  • finalize() 这个方法在子线程(垃圾回收线程)执行。
package day03;

public class Demo01 {

    public static void main(String[] args) {
/*      JVM的启动是多线程的吗?【面试题】
        1.JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
        2.main方法的代码执行的位置就是在主线程(路径)
        3.一个进程有多个线程
        4.finalize()这个方法在子线程(垃圾回收线程)执行*/

        System.out.println("AAAAA");
        System.out.println("BBBBB");
        System.out.println("CCCCC");
        System.out.println("DDDDD");

        //打印线程名称
        System.out.println(Thread.currentThread());//主线程
        for (int i = 0; i < 3; i++) {
            new Student();
            System.gc(); //启动垃圾回收
        }
    }
}

class Student {
    //被垃圾回收器回收时,会调用
    //对象从内存释放时,会调用 
    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("student 被回收了...");
        //打印线程名称
        System.out.println(Thread.currentThread());//子线程
    }
}

在这里插入图片描述

2. Java中线程的实现方式

2.1 方式一: 继承Thread

使用步骤:

  1. 定义一个类继承Thread类。
  2. 覆盖Thread类中的run方法。
  3. 直接创建Thread的子类对象创建线程。
  4. 调用start方法开启线程并调用线程的任务run方法执行。
package day03.lesson03;

public class demo0 {

    public static void main(String[] args) {

        for (int i = 1; i <= 12; i++) {
            ThreadDemo threadDemo = new ThreadDemo();
            threadDemo.start();
        }
    }
}

class ThreadDemo extends Thread {
    @Override
    public void run() {
        System.out.println("购买火车票任务……" + Thread.currentThread());
        System.out.println("线程名称:" + this.getName());
    }
}

在这里插入图片描述

注: 不能通过threadDemo.run()的方式来执行任务,因为这种试的任务是在主线程执行的。
正确的执行任务的方式,调用threadDemo.start()方法,内部会开启新线程,调用run方法。

  • P.S.
    • 可以通过ThreadgetName方法获取线程的名称,名称格式:Thread-编号(从0开始)
    • Thread在创建的时候,该Thread就已经命名了。源码如下:
      在这里插入图片描述

JVM创建的主线程的任务都定义在了主函数中。而自定义的线程,它的任务在哪儿呢?

  • Thread类用于描述线程,线程是需要任务的。所以Thread类也有对任务的描述。这个任务就是通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数,run方法中定义的就是线程要运行的任务代码。

总结:

  • 开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法,将运行的代码定义在run方法中即可

2.2 方式二: 实现Runnable接口

使用步骤:

  1. 定义类实现Runnable接口。
  2. 实现run方法。
  3. 把新线程要做的事写在run方法中。
  4. 创建自定义的Runnable的子类对象,创建Thread对象传入Runnable
  5. 调用start()开启新线程, 内部会自动调用Runnablerun()方法。
package day03;

public class demo02 {
    public static void main(String[] args) {
        //1.创建runable对象
        TrainTask task = new TrainTask();
        //2.创建Thread对象
        Thread t1 = new Thread(task);
        //3.启动线程
        t1.start();

        for (int i=1;i<=12;i++){
            Thread td = new Thread(task);
            td.start();
        }
    }
}

class TrainTask implements Runnable{

    @Override
    public void run() {
        System.out.println("购买火车票任务……" + Thread.currentThread());
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}

在这里插入图片描述

实现Runnable接口的好处:

1. 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
2. 避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。

2.3 两种方式的区别

  • 查看源码的区别:
    • 继承Thread : 由于子类重写了Thread类的run(), 当调用start()时直接找子类的run()方法。
    • 实现Runnable : 构造函数中传入了Runnable的引用, 有个成员变量记住了它, 调用run()方法时内部判断成员变量Runnable的引用是否为空。
      在这里插入图片描述
  • 继承Thread
    • 好处是: 可以直接使用Thread类中的方法,代码简单。
    • 弊端是: 如果已经有了父类,就不能用这种方法。
  • 实现Runnable接口
    • 好处是: 即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,代码更灵活。
    • 弊端是: 不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂。
      在这里插入图片描述

猜你喜欢

转载自www.cnblogs.com/blueheart-Yang/p/10010008.html