简体   繁体   English

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

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

I was going through basics of multithreading and was writing a program to understand the difference between using the 2 approaches of creating threads.我正在学习多线程的基础知识,并正在编写一个程序来了解使用这两种创建线程的方法之间的区别。

I have read that using Runnable allows multiple threads to share same object and wanted to try similar thing while extending Thread.我读过使用 Runnable 允许多个线程共享相同的 object 并且想在扩展 Thread 时尝试类似的事情。 So after creating new object of Demo2 I passed the reference to Thread constructor (similar to what we do in Runnable).因此,在创建 Demo2 的新 object 之后,我将引用传递给 Thread 构造函数(类似于我们在 Runnable 中所做的)。

I achieved what I was trying to as objT1, tT1, tT2 incremented value of sum to 3. But while printing out the current thread's name, it prints only demo2.1 .当 objT1, tT1, tT2 将 sum 的值增加到 3 时,我实现了我的目标。但是在打印出当前线程的名称时,它只打印demo2.1 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,因为我在构造函数中传递了这些名称。

    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);
    }
}

Output: Output:

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

So my question is - Does this snippet create 3 threads?所以我的问题是——这个片段是否创建了 3 个线程? If yes, then why aren't there 3 different names for each thread.如果是,那么为什么每个线程没有 3 个不同的名称。 If no, then what do these statements do.如果不是,那么这些语句的作用是什么。

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

I know this must be something trivial but I cannot get answers in tutorials.我知道这一定是微不足道的事情,但我无法在教程中得到答案。

Does this snippet create 3 threads?此代码段是否创建了 3 个线程?

Your program creates five threads.您的程序创建了五个线程。 Each thread is responsible for one of the five lines of output that you showed.每个线程负责您显示的 output 的五行之一。

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,因为我在构造函数中传递了这些名称。

The five threads are named "demo2.1", "t1", "t2", "tR1", and "tR2", but the "t1" and "t2" threads never get to print their own names.五个线程分别命名为“demo2.1”、“t1”、“t2”、“tR1”和“tR2”,但“t1”和“t2”线程永远无法打印自己的名称。 That's because you gave each one of them a Runnable delegate , which happens to be the "demo2.1" thread instance.那是因为你给了他们每个人一个Runnable delegate ,它恰好是“demo2.1”线程实例。

The run() method of the "demo2.1" instance (the run() method of the Demo2 class) prints its own name, which is not always the name of the thread that is running it. “demo2.1”实例的run() run()Demo2类的 run() 方法)打印它自己的名称,它并不总是运行它的线程的名称。

In more detail:更详细地:

Here, you create a new object, which is a Demo2 , and which is a Thread , and which implements Runnable , and which is named "demo2.1":在这里,您创建了一个新的 object,它是一个Demo2 ,它是一个Thread ,它implements Runnable ,它被命名为“demo2.1”:

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

When you call objT1.start() , the new thread calls the Demo2.run() method, and that method prints this.getName() .当您调用objT1.start()时,新线程调用Demo2.run()方法,并且该方法打印this.getName()

OK, That's simple, but the next bit is not so simple. OK,这很简单,但接下来的事情就没那么简单了。

Here, you create another new object, which is a Thread that uses the previous objT1 as its Runnable delegate, and which is named "t1":在这里,您创建另一个新的 object,它是一个使用之前的objT1作为其Runnable委托的Thread ,并命名为“t1”:

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

When you call tT1.start() , the new thread will call it's delegate's run() method.当您调用tT1.start()时,新线程将调用它的委托的run()方法。 It will call Demo2.run() .它将调用Demo2.run() But recall that Demo2 is a Thread with a name, and its run method prints its own name (actually, this.getName() ) instead of printing the name of the thread that is running it (which would be Thread.currentThread().getName() ).但是请记住, Demo2是一个有名称的Thread ,它的 run 方法打印它自己的名称(实际上是this.getName() )而不是打印运行它的线程的名称(可能是Thread.currentThread().getName() )。

Recap:回顾:

You are using objT1 in two different ways:您以两种不同的方式使用objT1

  1. You use it as a Thread您将其用作Thread
  2. You use it again, two more times, as the delegate of a different thread.您再次使用它两次,作为另一个线程的委托。

All three times, it prints its own name, which is not the same as the name of the thread that is running it in the two cases where you use the object as the delegate of a thread.所有三次,它都打印自己的名称,这与在您使用 object 作为线程委托的两种情况下运行它的线程的名称不同。

The Answer by Solomon Slow is informative, and excellent. Solomon Slow 的回答内容丰富,而且非常出色。

In addition, I'd like to add that in modern Java we rarely need to address the Thread class directly.此外,我想补充一点,在现代 Java 中,我们很少需要直接寻址Thread class。 The Executors framework was added in Java 5 to vastly simplify such code as yours.在 Java 5 中添加了 Executors 框架,以极大地简化您的此类代码。

The key concept is to separate the task(s) from the threads.关键概念是将任务与线程分开。 Focus on the work to be by defining a task as a Runnable (or Callable if returning a result).通过将任务定义为Runnable (如果返回结果则为Callable )来专注于要做的工作。

In your example, you seem to have two tasks that each result in incrementing a number, and you want to run each task twice.在您的示例中,您似乎有两个任务,每个任务都会导致一个数字递增,并且您希望每个任务运行两次。 So let's define two classes that implement Runnable .因此,让我们定义两个实现Runnable的类。 Both increment a counter, but only after pretending to do some amount of work.两者都增加了一个计数器,但只是在假装做了一些工作之后。 We simulate that work by sleeping some number of seconds.我们通过休眠几秒钟来模拟这项工作。 One sleeps a few seconds, the other sleeps longer, just to imagine two different workloads.一个睡几秒钟,另一个睡更长的时间,只是想像两种不同的工作负载。

Both classes carry a private member field of an AtomicInteger .这两个类都带有AtomicInteger的私有成员字段。 That class provides thread-safe ways to increment a number. class 提供了线程安全的方法来递增数字。 We need thread-safety protection because we are accessing the same number across threads.我们需要线程安全保护,因为我们正在跨线程访问相同的数字。

We mark the AtomicInteger member field as final to prevent us from inadvertently re-assigning another object, as we might do during future edits to this code.我们将AtomicInteger成员字段标记为final字段,以防止我们无意中重新分配另一个 object,就像我们在以后编辑此代码时可能会做的那样。

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();
    }
}

And the slower version.和较慢的版本。

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();
    }
}

Instantiate each of those tasks.实例化每一个任务。

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

Instantiate an ExecutorService to handle the threading on our behalf.实例化一个ExecutorService来代表我们处理线程。 Usually we get an executor service by way of Executors utility class.通常我们通过Executors实用程序 class 获得执行器服务。

Here we use Executors.newCachedThreadPool() for an executor service that creates any number of threads as needed.在这里,我们使用Executors.newCachedThreadPool()作为执行程序服务,根据需要创建任意数量的线程。 This is appropriate in situations where we know we will use a limited number of threads.这适用于我们知道我们将使用有限数量线程的情况。

ExecutorService executorService = Executors.newCachedThreadPool();

Your example runs each task twice.您的示例将每个任务运行两次。 So we submit each task twice to our executor service.所以我们将每个任务提交给我们的执行者服务两次。

Remember that both of our classes, FastCalc & SlowCalc , implements Runnable .请记住,我们的两个类FastCalcSlowCalc实现了Runnable So we are passing Runnable objects to the submit method here.所以我们在这里将Runnable对象传递给submit方法。

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

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

Then we wait for the tasks to complete.然后我们等待任务完成。 We do this by calling a method that we pulled as boilerplate from the Javadoc of ExecutorService .我们通过调用我们从ExecutorService的 Javadoc 作为样板提取的方法来做到这一点。 We changed that code a bit to pass Duration as the amount of time we should reasonably wait for tasks to complete.我们稍微更改了该代码以将Duration作为我们应该合理等待任务完成的时间量传递。

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

Here is that boilerplate.这是样板文件。

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();
    }
}

Lastly, we want to report on the results of the run.最后,我们要报告运行结果。

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

Pulling that code together.将该代码拉到一起。

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();
        }
    }
}

When run.运行时。

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

Regarding your original interest in total number of threads, we can see here by the thread ID numbers that this code uses a total of 4 threads, one thread per task submission.关于您最初对线程总数的兴趣,我们可以在这里通过线程 ID 号看到这段代码总共使用 4 个线程,每个任务提交一个线程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM