通过扩展在 Java 中共享相同对象的 Thread class 创建多个线程

Create multiple threads by extending Thread class that share same object in Java

我正在学习多线程的基础知识,并且正在编写一个程序来了解使用这两种创建线程的方法之间的区别。

我读到使用 Runnable 允许多个线程共享同一个对象,并且想在扩展 Thread 时尝试类似的事情。因此,在创建 Demo2 的新对象后,我将引用传递给 Thread 构造函数(类似于我们在 Runnable 中所做的)。

当 objT1、tT1、tT2 将 sum 的值增加到 3 时,我实现了我的目标。但是在打印当前线程的名称时,它只打印 demo2.1。我认为将要打印的线程名称是 demo2.1、t1、t2,因为我在构造函数中传递了这些名称。

    class Main {
    public static void main(String args[]) {
        Demo1 objR1 = new Demo1();
        Demo2 objT1 = new Demo2("demo2.1");

        Thread tT1 = new Thread(objT1,"t1");
        Thread tT2 = new Thread(objT1,"t2");

        Thread tR1 = new Thread(objR1,"tR1");
        Thread tR2 = new Thread(objR1,"tR2");
    
        objT1.start();
        tT1.start();
        tT2.start();

        tR1.start();
        tR2.start();
    }
}


class Demo1 implements Runnable {

    int sum = 0;

    synchronized void calculate() {
        sum = sum +1;   
    }

    public void run()
    {
        calculate();    
        System.out.print(Thread.currentThread().getName()); 
        System.out.println(" "+sum);
    }
}

class Demo2 extends Thread {

    int sum = 0;

    Demo2(String n) {
        super(n);   
    }
    
    synchronized void calculate() {
        sum = sum +1;       
    }

    public void run()
    {
        calculate();        
        System.out.println(this.getName()+" "+sum);
    }
}

输出:

demo2.1 1
demo2.1 2
demo2.1 3
tR1 1
tR2 2

所以我的问题是 - 此代码段是否创建了 3 个线程?如果是,那么为什么每个线程没有 3 个不同的名称。如果不是,那么这些语句的作用是什么。

Demo2 objT1 = new Demo2("demo2.1");
Thread tT1 = new Thread(objT1,"t1");
Thread tT2 = new Thread(objT1,"t2"); 

我知道这一定是小事,但我无法在教程中找到答案。

Does this snippet create 3 threads?

您的程序创建了五个线程。每个线程负责您显示的五行输出中的一行。

I thought that the thread names that will be printed would be demo2.1, t1, t2 since I passed these names in constructor.

这五个线程被命名为“demo2.1”、“t1”、“t2”、“tR1”和“tR2”,但是“t1”和“t2”线程永远无法打印自己的名字.那是因为你给了他们每个人一个Runnabledelegate,恰好是“demo2.1”线程实例

“demo2.1”实例的run()方法(Demo2class的run()方法)打印自己的名字,不一定运行连接它的线程的名称。

更详细:

在这里,您创建一个新对象,一个 Demo2,一个 Thread,一个 implements Runnable,并命名为“demo2.1”:

Demo2 objT1 = new Demo2("demo2.1")

当您调用 objT1.start() 时,新线程调用 Demo2.run() 方法,该方法打印 this.getName().

OK,这很简单,但接下来的一点就没那么简单了。

在这里,您创建另一个新对象,它是一个 Thread,它使用之前的 objT1 作为它的 Runnable 委托,并且被命名为“t1”:

Thread tT1 = new Thread(objT1,"t1");

当您调用 tT1.start() 时,新线程将调用它的委托的 run() 方法。它将调用 Demo2.run()。但是回想一下 Demo2 是一个有名字的 Thread,它的 运行 方法打印它的 own 名字(实际上,this.getName())而不是打印正在 运行 的线程的名称(应该是 Thread.currentThread().getName())。

回顾:

您正在以两种不同的方式使用 objT1

  1. 您将其用作 Thread
  2. 您再次使用它,再使用两次,作为 不同 线程的委托。

全部三次,它打印它的 own 名称,这与在以下两种情况下 运行ning 它的线程名称不同您将该对象用作线程的委托。

内容丰富,非常出色。

此外,我想补充一点,在现代 Java 中,我们很少需要直接处理 Thread class。 Java 5 中添加了 Executors 框架,以极大地简化您的代码。

关键概念是将任务与线程分开。通过将任务定义为 Runnable (or Callable(如果返回结果)来专注于工作。

在您的示例中,您似乎有两个任务,每个任务都会导致一个数字递增,并且您想 运行 每个任务两次。因此,让我们定义两个实现 Runnable 的 classes。两者都增加了一个计数器,但只是在假装做了一些工作之后。我们通过休眠几秒钟来模拟这项工作。一个睡几秒钟,另一个睡更长的时间,只是想像两种不同的工作量。

两个 classes 都带有 AtomicInteger 的私有成员字段。 class 提供了 thread-safe 增加数字的方法。我们需要 thread-safety 保护,因为我们正在跨线程访问相同的数字。

我们将 AtomicInteger 成员字段标记为 final 以防止我们无意中 re-assigning 另一个对象,就像我们在以后编辑此代码时可能会做的那样。

public class FastCalc implements Runnable
{
    private final AtomicInteger counter = new AtomicInteger();

    @Override
    public void run ( )
    {
        System.out.println( "INFO - starting `run` on `FastCalc` at " + Instant.now() + " on thread ID " + Thread.currentThread().getId() );  // Beware: Output does *not* necessarily appear on console in chronological order.
        try { Thread.sleep( ThreadLocalRandom.current().nextInt( 2_000 , 4_000 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
        int currentCount = this.counter.incrementAndGet();
        System.out.println( "INFO - result of `run` on `FastCalc` at " + Instant.now() + " is: " + currentCount );
    }

    public int report ( )
    {
        return this.counter.get();
    }
}

和较慢的版本。

public class SlowCalc implements Runnable
{
    private final AtomicInteger counter = new AtomicInteger();

    @Override
    public void run ( )
    {
        System.out.println( "INFO - starting `run` on `SlowCalc` at " + Instant.now() + " on thread ID " + Thread.currentThread().getId() );  // Beware: Output does *not* necessarily appear on console in chronological order.
        try { Thread.sleep( ThreadLocalRandom.current().nextInt( 8_000 , 12_000 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
        int currentCount = this.counter.incrementAndGet();
        System.out.println( "INFO - result of `run` on `SlowCalc` at " + Instant.now() + " is: " + currentCount );
    }

    public int report ( )
    {
        return this.counter.get();
    }
}

实例化每个任务。

FastCalc taskFast = new FastCalc();  // Implements `Runnable`.
SlowCalc taskSlow = new SlowCalc();  // Implements `Runnable`.

实例化一个 ExecutorService 来代表我们处理线程。通常我们通过 Executors 实用程序 class.

获得执行程序服务

这里我们使用 Executors.newCachedThreadPool() 作为执行器服务,根据需要创建任意数量的线程。这适用于我们知道我们将使用有限数量线程的情况。

ExecutorService executorService = Executors.newCachedThreadPool();

您的示例 运行 每个任务两次。所以我们将每个任务提交两次给我们的执行者服务。

请记住,我们的 classes FastCalcSlowCalc 都实现了 Runnable。所以我们在这里将 Runnable 个对象传递给 submit 方法。

executorService.submit( taskFast );  // Passing a `Runnable` object.
executorService.submit( taskSlow );

executorService.submit( taskFast );
executorService.submit( taskSlow );

然后等待任务完成。我们通过调用一个方法来实现这一点,该方法作为样板从 ExecutorService. We changed that code a bit to pass Duration 的 Java 文档中提取,作为我们应该合理等待任务完成的时间量。

this.shutdownAndAwaitTermination( executorService , Duration.ofMinutes( 1 ) );

这是样板文件。

void shutdownAndAwaitTermination ( ExecutorService executorService , Duration duration )
{
    executorService.shutdown(); // Disable new tasks from being submitted
    try
    {
        // Wait a while for existing tasks to terminate
        if ( ! executorService.awaitTermination( duration.toSeconds() , TimeUnit.SECONDS ) )
        {
            executorService.shutdownNow(); // Cancel currently executing tasks
            // Wait a while for tasks to respond to being cancelled
            if ( ! executorService.awaitTermination( duration.toSeconds() , TimeUnit.SECONDS ) )
            { System.err.println( "Pool did not terminate" ); }
        }
    }
    catch ( InterruptedException ex )
    {
        // (Re-)Cancel if current thread also interrupted
        executorService.shutdownNow();
        // Preserve interrupt status
        Thread.currentThread().interrupt();
    }
}

最后,我们要报告 运行 的结果。

System.out.println("Report — taskFast counter: " + taskFast.report() );
System.out.println("Report — taskSlow counter: " + taskFast.report() );

将代码整合在一起。

package work.basil.example.threading;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class App2
{
    public static void main ( String[] args )
    {
        App2 app = new App2();
        app.demo();
    }

    private void demo ( )
    {
        System.out.println( "INFO - Start running demo. " + Instant.now() );

        FastCalc taskFast = new FastCalc();  // Implements `Runnable`.
        SlowCalc taskSlow = new SlowCalc();  // Implements `Runnable`.

        ExecutorService executorService = Executors.newCachedThreadPool();

        executorService.submit( taskFast );  // Passing a `Runnable` object.
        executorService.submit( taskSlow );

        executorService.submit( taskFast );
        executorService.submit( taskSlow );

        this.shutdownAndAwaitTermination( executorService , Duration.ofMinutes( 1 ) );

        System.out.println( "Report — taskFast counter: " + taskFast.report() );
        System.out.println( "Report — taskSlow counter: " + taskFast.report() );

        System.out.println( "INFO - End running demo. " + Instant.now() );
    }


    // Boilerplate pulled from Javadoc of `ExecutorService`.
    void shutdownAndAwaitTermination ( ExecutorService executorService , Duration duration )
    {
        executorService.shutdown(); // Disable new tasks from being submitted
        try
        {
            // Wait a while for existing tasks to terminate
            if ( ! executorService.awaitTermination( duration.toSeconds() , TimeUnit.SECONDS ) )
            {
                executorService.shutdownNow(); // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if ( ! executorService.awaitTermination( duration.toSeconds() , TimeUnit.SECONDS ) )
                { System.err.println( "Pool did not terminate" ); }
            }
        }
        catch ( InterruptedException ex )
        {
            // (Re-)Cancel if current thread also interrupted
            executorService.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }
    }
}

当运行.

INFO - Start running demo. 2022-05-11T20:50:36.796870Z
INFO - starting `run` on `FastCalc` at 2022-05-11T20:50:36.809083Z on thread ID 16
INFO - starting `run` on `SlowCalc` at 2022-05-11T20:50:36.809228Z on thread ID 17
INFO - starting `run` on `SlowCalc` at 2022-05-11T20:50:36.808793Z on thread ID 15
INFO - starting `run` on `FastCalc` at 2022-05-11T20:50:36.808714Z on thread ID 14
INFO - result of `run` on `FastCalc` at 2022-05-11T20:50:40.081938Z is: 1
INFO - result of `run` on `FastCalc` at 2022-05-11T20:50:40.385796Z is: 2
INFO - result of `run` on `SlowCalc` at 2022-05-11T20:50:47.620290Z is: 1
INFO - result of `run` on `SlowCalc` at 2022-05-11T20:50:47.699582Z is: 2
Report — taskFast counter: 2
Report — taskSlow counter: 2
INFO - End running demo. 2022-05-11T20:50:47.703597Z

关于您最初对线程总数的兴趣,我们可以在这里通过线程 ID 号看到这段代码总共使用了 4 个线程,每个任务提交一个线程。