マルチスレッド - スレッドの作成

1. Thread クラスの概要

1.1 実装されたインターフェース

4e67de866a9c431a901c0fb8768d986c.png

 1.2 施工方法

6875f335b3db4b86ac77c38670cbf211.png

 1.3 一般的な方法

622553a9122e46b18f1dbbd779f26028.png

1) クラスを Thread のサブクラスとして宣言します。サブクラスは、Thread クラスの run メソッドをオーバーライドする必要があります。オブジェクトを作成し、スレッドを開始します。run メソッドは他のスレッドの main メソッドと同等です。

2) Runnable インターフェイスを実装するクラスを宣言します。次に、クラスは run メソッドを実装します。次に、Runnable のサブクラス オブジェクトを作成し、それをスレッドのコンストラクターに渡し、スレッドを開始します。

2. スレッド作成方法1:Threadクラスを継承する

Thread クラスを継承してマルチスレッドを実装する手順は次のとおりです。

        1) クラスを定義し、Thread クラスを継承します。

        2) Thread クラスの run() メソッドを書き換えます run() メソッドには、スレッドが実行する必要があるタスクが含まれています。

        3) Thread のサブクラスを呼び出してスレッド オブジェクトを作成します。

        4) start() メソッドを呼び出してスレッドを開始します スレッドの開始後、スレッドの run() メソッドが自動的に呼び出されます。

【例】Threadクラスを継承してマルチスレッドを実装する

/**

 * カスタムスレッドクラス

 */

クラスTestThread extends Thread {

    パブリックTestThread() {}

    public TestThread(文字列名) {

        super (name); //スレッド名を設定する

    }

    @オーバーライド

    public void run() {

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

            // Thread の getName() メソッドを通じてスレッド名を取得します

            システム。out .println( this .getName() + "--->" + i);

        }

    }

}

/**

 * テストクラス

 */

パブリック クラステスト {

    public static void main(String[] args) {

        //スレッドオブジェクトを作成

        TestThread th1 = new TestThread("スレッド");

        TestThread th2 = new TestThread("B スレッド");

        // スレッドの開始

        th1.start(); // 注: スレッド オブジェクトの start() メソッドは 1 回だけ呼び出すことができます。

        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

実行結果を観察した結果、シミュレーションした起動方法に問題がないことが分かりましたので、この例をきっかけに Thread クラスへの理解を深めていただければ幸いです。

 

4: スレッドを作成するメソッド 3: スレッドを作成するための Lamda 式

Lambda 式は JDK8 から始まりました。現時点では、匿名内部クラスの代わりにラムダ式を作成できます。Lambda を使用すると、匿名内部クラスの操作を簡素化できます。

パッケージ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); 
            } 
        }) 。始める(); 
    } 
}

 

 

おすすめ

転載: blog.csdn.net/shengshanlaolin_/article/details/127468714