多线程——线程的创建

一、Thread类介绍

1.1 实现的接口

4e67de866a9c431a901c0fb8768d986c.png

 1.2 构造方法

6875f335b3db4b86ac77c38670cbf211.png

 1.3 常用方法

622553a9122e46b18f1dbbd779f26028.png

1)将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建对象,开启线程。run 方法相当于其它线程的 main 方法。

2)声明一个实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable 的子类对象,传入到某个线程的构造方法中,开启线程。

二、创建线程方式一:继承Thread类

继承Thread类实现多线程的步骤:

        1)定义一个类并继承于Thread类。

        2)重写Thread类的run()方法,run()方法中包含了线程需要执行的任务。

        3)调用的Thread的子类来创建线程对象。

        4)通过调用start()方法开启线程,线程开启后会自动调用线程的run()方法。

【示例】继承Thread类实现多线程

/**

 * 自定义线程类

 */

class TestThread extends Thread {

    public TestThread() {}

    public TestThread(String name) {

        super(name); // 设置线程名字

    }

    @Override

    public void run() {

        for (int i = 0; i < 10; i++) {

            // 通过Thread的getName()方法获取线程名字

            System.out.println(this.getName() + "--->" + i);

        }

    }

}

/**

 * 测试类

 */

public class Test {

    public static void main(String[] args) {

        // 创建线程对象

        TestThread th1 = new TestThread("A线程");

        TestThread th2 = new TestThread("B线程");

        // 启动线程

        th1.start(); // 注意:一个线程对象的start()方法只能被调用一次。

        th2.start();

        for(int i = 0; i < 10; i++) {

            System.out.println("主线程:" + i);

        }

    }

}

运行以上案例代码,输出结果如下:

 937a61f67805407986b69d4a9d95ed92.png

思考1:线程对象调用run方法和调用start方法区别?

线程对象调用run方法不开启线程,仅是对象调用方法并在主线程中执行。线程对象调用start开启线程,并让JVM调用run方法在开启的线程中执行。

思考2:我们为什么要继承 Thread 类,并调用其的 start 方法才能开启线程呢?

继承 Thread 类:因为 Thread 类用来描述线程,具备线程应该有功能。那为什么不直接创建 Thread类的对象呢?如下代码:

public static void main(String[] args) {

    Thread th = new Thread();

    th.start();

}

以上代码语法上没有任何问题,但是该 start 调用的是 Thread 类中的 run 方法,而这个 run 方法没有做什么事情,更重要的是这个 run 方法中并没有定义我们需要让线程执行的代码。

创建线程的目的就是为了建立程序单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定线程要执行的任务。对于之前所讲的主线程,它的任务定义在 main 函数中。自定义线程需要执行的任务都定义在 run方法中。

Thread 类 run 方法中的任务并不是我们所需要的,只有重写这个 run 方法。既然 Thread 类已经定义了线程任务的编写位置(run 方法),那么只要在编写位置(run 方法)中定义任务代码即可,所以进行了重写 run 方法动作。

思考3:多线程执行时,到底在内存中是如何运行的呢?

多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。

977d3a0ffa0a42889c396298968f2695.png

在多线程中,每个线程都有自己独立的栈内存,但是都是共享的同一个堆内存。在某个线程中程序执行出现了异常,那么对应线程执行终止,但是不影响别的线程程序执行。

main方法执行完毕之后,虚拟机有可能不会立即结束,只有等所有的线程都执行完毕之后,虚拟机才会结束!

思考4:开启的线程都会有自己的独立运行栈内存,那么这些运行的线程的名字是什么呢?

查阅Thread类的API文档发现有个方法是获取当前正在运行的线程对象,还有个方法是获取当前线程对象的名称。

80fdf077479e44bea53d7c000c6d438c.png

想要获取运行时线程名称,必须先要得到运行时线程对象(这里的线程对象和继承Thread子类对象是不一样的)。在线程类方法当中有一个方法,叫做currentThread(),返回thread类型,静态的,类名可以直接调用。

【示例】获取当前线程对象和线程名称

/**

 * 自定义线程类

 */

class TestThread extends Thread {

    private String name; // 定义一个普通成员变量

    public TestThread(String name) {

        this.name = name; // 给成员变量赋值

    }

    @Override

    public void run() {

        // 获取当前线程名称

        String threadName = Thread.currentThread().getName();

        System.out.println(name + "线程名称:" + threadName);

    }

}

/**

 * 测试类

 */

public class Test {

    public static void main(String[] args) {

        // 获取主线程对象

        Thread main = Thread.currentThread();

        // 获取主线程名称

        String name = main.getName();

        System.out.println("主线程名称:" + name);

        // 创建线程对象

        TestThread th1 = new TestThread("th1");

        TestThread th2 = new TestThread("th2");

        // 启动线程

        th1.start();

        th2.start();      

    }

}

运行以上案例代码,输出结果如下:

a05e4ea9622e4d27ad6427a470c3b789.png

通过运行结果观察,发现主线程的名称为:main。自定义的线程名字默认为:Thread-加上编号,编号从0开始递增,th1线程对应的名称为:Thread-0,th2线程对应的名称为Thread-1。

    那么自定义线程的默认名字是怎么来的呢? 通过对Thread类的源码分析,我们发现调用Thread类的构造方法时,默认就给该线程对象定义了一个名字,格式为:Thread-加上编号。4866ee62d2ee47289f7db97128db1dc2.png

  由此,我们也可以得出一个结论:当我们创建线程子类对象的时候,它们在创建的同时已经完成了名称的定义。

【示例】获取线程对象的名称

public class Test {

    public static void main(String[] args) {

        // 创建线程对象

        Thread th1 = new Thread();

        Thread th2 = new Thread();

        // 获取创建线程对象的名称

        System.out.println("th1线程对象名称:" + th1.getName());

        System.out.println("th2线程对象名称:" + th2.getName());      

    }

}

运行以上案例代码,输出结果如下:

b6cfa3e9903544a595937e34db85e2aa.png

思考5:可以手动的设置线程名称吗?

自定义的线程名字默认为:Thread-加上编号,如果我们想要修改默认的线程名字,可以在创建线程对象的时候设置线程的名称,也可以使用Thread类提供的setName()方法来实现。

b9d1e3bf695b48a9a19616e80a3081fc.png

【示例】设置线程对象的名称

public class Test {

    public static void main(String[] args) {

        // 创建线程对象

        Thread th = new Thread();

        // 设置线程的名称

        th.setName("线程A");

        // 获取创建线程对象的名称

        System.out.println("th线程对象名称:" + th.getName());

    }

}

运行以上案例代码,输出结果如下:

5c9ca175ef3544dd9c32cd5890365992.png

三:创建线程方式二:实现Runnable接口

使用继承Thread类的方式来创建线程有一个缺点,那就是自定义的类继承Thread类后就不能继承别的父类,如果还想继承别的父类那么可以选用第二种创建线程的方式。

在开发中,我们更多的是通过Runnable接口实现多线程,使用这种方式避免了Java单继承的局限性,所以实现Runnable接口方式要通用一些。

查看 Runnable 接口说明文档:Runnable 接口用来指定每个线程要执行的任务。包含了一个run的无参数抽象方法,需要由接口实现类重写该方法。

44c00bbcdd69422aa8f2a95845eb1677.png

  • 接口中的方法

7447f9a0bad84cc5b059aed7a7c9cbda.png

  • Thread 类构造方法

29dbc6cc96b54bb989d14b3589870a0c.png

实现Runnable接口实现多线程的步骤:

        1)定义一个类并实现Runnable接口。

        2)重写Runnable接口的run()方法,在run()方法中包含线程需要执行的任务。

        3)通过Thread类创建线程对象,并把Runnable接口的实现类对象作为参数传递。

        4)调用线程对象的start()方法开启线程,并调用Runnable实现类的run()方法。

【示例】实现Runable接口实现多线程

/**

 * Runnable接口的实现类

 */

class TestRunnable implements Runnable {

    @Override

    public void run() {

        for(int i = 0; i < 10; i++) {

            System.out.println("线程名称:" + Thread.currentThread().getName());

        }

    }

}

/**

 * 测试类

 */

public class Test {

    public static void main(String[] args) {

        // 创建线程执行任务对象

        TestRunnable tr = new TestRunnable();

        // 创建线程对象,将tr作为参数传递给Thread类的构造函数

        Thread th1 = new Thread(tr, "线程A");

        Thread th2 = new Thread(tr, "线程B");

        // 启动线程

        th1.start();

        th2.start();

        for(int i = 0; i < 10; i++) {

            System.out.println("线程名称:" + Thread.currentThread().getName());

        }

    }

}

运行以上案例代码,输出结果如下:

c8e66552d96f49798402b1784460d6a8.png

Thread 类用来描述线程,使其具备线程应该有功能,Runnable接口实现类用来封装线程任务,从而实现线程对象和线程任务进行解耦。

实现Runnable接口的好处,不但避免了java单继承的局限性,而且还将线程任务从线程子类相分离,进行了单独的封装,按照面向对象的思想将任务封装成对象。

~模拟Thread实现start方法:

我们知道,通过继承Thread类创建线程,线程任务是封装在Thread子类的run方法中;通过实现Runnable接口来创建线程,线程任务是封装在Runnable接口实现类的run方法中,那么调用start方法开启线程,在Thread内部是如何正确的执行线程任务的呢?

接下来我们就来模拟实现Thread类,明确调用start方法开启线程调用之后是如何实现调用run方法来执行对应的线程任务。

【示例】模拟Thread类start方法的实现

class Thread {

    private Runnable target;

    public Thread() {}

    public Thread(Runnable r) {

        this.target = r;

    }

    public void start() {

        run(); // 注意此处是重点

    }

    public void run() {

        if(target != null)

            target.run(); 

    }

}

模拟Thread类start方法的实现的核心:

如果创建线程采用继承Thread类的方式,也就是通过Thread子类对象调用start方法,那么调用的就是Thread子类对象的run方法。

如果创建线程采用实现Runnable接口的方式,也就是通过Thread对象调用start方法,那么调用的就是Thread的run方法,然后再去调用Runnable接口实现类的run。

接下来,我们就基于两种创建线程的方式,来对我们模拟Thread类的start方法进行测试,看一下我们模拟实现是否成功!

【示例】基于两种创建线程方式的测试

// 模拟Thread类的start方法实现

class Thread {

    private Runnable target;

    public Thread() {}

    public Thread(Runnable r) {

        this.target = r;

    }

    public void start() {

        run();

    }

    public void run() {

        if(target != null)

            target.run(); 

    }

}

// Runnable接口的实现类

class MyRunnable implements Runnable {

    @Override

    public void run() {

        System.out.println("MyRunnable run....");

    }

}

// 继承于Thread的子类

class MyThread extends Thread {

    public void run() {

        System.out.println("MyThread run....");

    }

}

// 测试类

public class Test01 {

    public static void main(String[] args) {

        // 创建多线程方式一:继承Thread类

        MyThread th1 = new MyThread();

        th1.start();

        // 创建多线程方式二:实现Runnable接口

        MyRunnable mr = new MyRunnable();

        Thread th2 = new Thread(mr);

        th2.start();

    }

}

运行以上案例代码,输出结果如下:

451ba961ab134cb39af6ec8ee2736a5c.png

通过运行结果观察,发现我们模拟实现的start方法没有任何问题,希望通过这个例子能够加深同学们对Thread类的理解!

四:创建线程方式三:Lamda表达式创建线程

Lambda表达式起始于JDK8,此时可以代替匿名内部类创建,使用Lambda可以简化匿名内部类操作。

package com.bjpowernode.day17.lambda;
  public class LambdaTest {
    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 1; i <=5 ; i++) {
                System.out.println(i);
            }
        }).start();
    }
}

猜你喜欢

转载自blog.csdn.net/shengshanlaolin_/article/details/127468714