创建和启动Java线程

本文详细介绍了如何在Java中创建和启动线程,包括两种方式:继承Thread类和实现Runnable接口。通过示例代码展示了线程的命名、启动、暂停与停止,并强调了调用start()而非run()的重要性。同时,讨论了使用Runnable接口的优势以及线程同步的初步概念。
摘要由CSDN通过智能技术生成

本文翻译自http://tutorials.jenkov.com/java-concurrency/creating-and-starting-threads.html,机翻加人工校正,仅供学习交流。

创建和启动Java线程

Java线程就像一个虚拟CPU,可以在Java应用程序内部执行Java代码。当一个Java应用程序启动时,它的main()方法由主线程执行,Java 虚拟机会创建一个特殊的线程来运行您的应用程序。在主线程中,您的应用程序可以创建并启动多个并行执行应用程序代码的部分的线程。Java线程与任何其他Java对象一样是对象。线程是类java.lang.Thread的实例,或者这个类的子类的实例。除了作为对象之外,java线程还可以执行代码。在这个Java线程教程中,我将解释如何创建和启动线程。

Java线程视频教程

如果你喜欢视频,这里是这个Java线程教程的视频版本。
https://www.youtube.com/watch?v=eQk5AWcTS8w

创建和启动线程

在Java中创建一个线程是这样做的:

 Thread thread = new Thread();

要启动Java线程,你需要调用它的start()方法,如下所示:

  thread.start();

这个例子没有指定要执行的线程的任何代码。因此,线程将再次停止.。
有两种方法可以指定线程应该执行什么代码。第一个是创建Thread的子类并重写run()方法。第二种是在线程构造函数中传递一个实现Runnable (java.lang. lang)的对象。下面将介绍这两种方法。

线程子类

指定线程要运行的代码的第一种方法是创建Thread的子类并重写run()方法。run()方法是在调用start()之后由线程执行的方法。下面是一个创建Java Thread子类的示例:

 public class MyThread extends Thread {
    public void run(){
       System.out.println("MyThread running");
    }
  }

要创建和启动上面的线程,你可以这样做:

  MyThread myThread = new MyThread();
  myTread.start();

start()调用将在线程启动后立即返回,它不会等到run()方法完成。run()方法将执行,就像由不同的CPU执行一样。当run()方法执行时,它将打印出文本“MyThread running”。
你也可以像这样创建一个匿名的Thread子类:

  Thread thread = new Thread(){
    public void run(){
      System.out.println("Thread Running");
    }
  }
  thread.start();

一旦run()方法被新的t执行,这个例子将打印出文本“Thread running”。

Runnable接口实现

指定线程应该运行的代码的第二种方法是创建一个实现java.lang.Runnable接口的类。实现了Runnable接口的Java对象可以由Java线程执行。如何做到这一点将在本教程稍后的部分展示。
Runnable接口是Java平台的标准Java接口。Runnable接口只有一个run()方法。以下是Runnable接口的基本外观:

public interface Runnable() {
    public void run();
}

不管线程应该做什么,它必须执行实现run()方法中的内容。有三种方法来实现Runnable接口:

  1. 创建一个实现Runnable接口的Java类。
  2. 创建一个实现Runnable接口的匿名类。
  3. 创建一个实现Runnable接口的Java Lambda。
    下面的部分将解释这三个选项。
Java类实现Runnable

实现Java Runnable接口的第一种方法,通过创建自己的Java类来实现Runnable接口。下面是一个实现Runnable接口的自定义Java类的示例:

 public class MyRunnable implements Runnable {

    public void run(){
       System.out.println("MyRunnable running");
    }
  }

所有这些Runnable实现所做的就是打印出MyRunnable running的文本。打印该文本后,run()方法退出,运行run()方法的线程将停止。

Runnable的匿名实现

您还可以创建Runnable的匿名实现。下面是一个实现Runnable接口的匿名Java类的例子:

Runnable myRunnable =
    new Runnable(){
        public void run(){
            System.out.println("Runnable running");
        }
    }

除了是一个匿名内部类,这个例子与使用自定义类实现Runnable接口的例子非常相似。

Runnable的Java Lambda实现

实现Runnable接口的第三种方法是通过创建Runnable接口的Java Lambda实现。Runnable接口只有一个未实现的方法,因此实际上(尽管可能是无意的)是一个函数式接口。
下面是一个实现Runnable接口的Java lambda表达式示例:

Runnable runnable =
        () -> { System.out.println("Lambda Runnable running"); };
使用Runnable启动线程

要让一个线程执行run()方法,传递一个在其构造函数中实现到线程的Runnable接口的类的实例、匿名类或lambda表达式。这是如何做到的:

Runnable runnable = new MyRunnable(); // or an anonymous class, or lambda...
Thread thread = new Thread(runnable);
thread.start();

线程启动时,它将调用MyRunnable实例的run()方法。而不是执行它自己的run()方法。上面的例子会打印出文本“MyRunnable running”。

子类还是Runnable吗?

没有规定哪一种方法是最好的,这两种方法都有效。但就我个人而言,我更喜欢实现Runnable,并将实现的实例传递给Thread实例。在线程池中执行Runnable时,在池中的线程空闲之前让Runnable实例排队是很容易的。这对于Thread子类来说有点困难。
有时你可能必须实现Runnable以及Thread的子类,例如,如果创建一个可以执行多个Runnable的Thread的子类。这是实现线程池时的典型情况。

常见陷阱:调用run()而不是start()

当创建和启动线程时,一个常见的错误是调用Thread的run()方法而不是start(),像这样

Thread newThread = new Thread(MyRunnable());
  newThread.run();  //should be start();

起初您可能不会注意到任何事情,因为Runnable的run()方法像您期望的那样被执行,但是,它不是由您刚刚创建的新线程执行的,run()方法是由创建线程的线程执行的。换句话说,执行上述两行代码的线程。要让新创建的线程调用MyRunnable实例的run()方法,你必须调用newThread.start()方法。

线程的名字

当您创建一个Java线程时,您可以给它一个名称。这个名称可以帮助您区分不同的线程。例如,如果多个线程写入System.out,它可以方便地查看哪个线程写的文本。这里有一个例子

   Thread thread = new Thread("New Thread") {
      public void run(){
        System.out.println("run by: " + getName());
      }
   };
   thread.start();
   System.out.println(thread.getName());

注意字符串“New Thread”作为参数传递给Thread构造函数,这个字符串是线程的名称。该名称可以通过Thread的getName()方法获得,在使用Runnable实现时,还可以向线程传递一个名称,这是它的样子:

   MyRunnable runnable = new MyRunnable();
   Thread thread = new Thread(runnable, "New Thread");

   thread.start();
   System.out.println(thread.getName());

注意这一点,因为MyRunnable类不是Thread的子类,它不能访问执行它的线程的getName()方法。

Thread.currentThread()

Thread.currentThread()方法返回对当前执行的线程实例的引用。通过这种方式,您可以访问执行给定代码块的线程对象。下面是一个如何使用Thread.currentThread()的例子:

Thread thread = Thread.currentThread();

一旦有了对Thread对象的引用,就可以调用它的方法。例如,你可以像这样获取当前执行代码的线程的名称:

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

Java线程的例子

这里有一个简单的例子。首先,它打印出执行main()方法的线程的名称,这个线程由JVM分配。然后启动10个线程,并给它们一个数字作为名称(“”+i)。然后每个线程打印出它的名称,然后停止执行。

public class ThreadExample {
  public static void main(String[] args){
    System.out.println(Thread.currentThread().getName());
    for(int i=0; i<10; i++){
      new Thread("" + i){
        public void run(){
          System.out.println("Thread: " + getName() + " running");
        }
      }.start();
    }
  }
}

请注意,即使线程按顺序启动(1、2、3等)。它们可能不会按顺序执行,这意味着线程1可能不是第一个将其名称写入System.out的线程。这是因为这些线程原则上是并行执行的,而不是顺序执行的。JVM或操作系统决定线程执行的顺序,这个顺序不必与它们开始时的顺序相同。

暂停一个线程

线程可以通过调用静态方法thread .sleep()来暂停自己,sleep()以毫秒数作为参数。sleep()方法将在恢复执行之前尝试休眠该毫秒数。线程睡眠()并不是100%准确,但它仍然很好用。下面是一个通过调用thread .sleep()来暂停一个Java线程3秒(3000毫秒)的例子:

try {
    Thread.sleep(10L * 1000L);
} catch (InterruptedException e) {
    e.printStackTrace();
}

执行上述Java代码的线程将休眠大约10秒(10.000毫秒)

停止一个线程

停止Java线程需要准备一些线程实现代码,Java Thread类包含一个stop()方法,但已弃用。原来的stop()方法不能保证线程处于停止状态,这意味着线程在执行期间访问的所有Java对象都将处于未知状态。如果应用程序中的其他线程也可以访问相同的对象,您的应用程序可能会意外地、不可预测地失败。
取代调用stop()方法,你必须实现你的线程代码,使它可以停止。这里有一个例子,一个包含额外的向Runnable发出停止信号的方法doStop()的实现Runnable的类,Runnable将检查这个信号,并在准备好这样做时停止。

public class MyRunnable implements Runnable {
    private boolean doStop = false;
    public synchronized void doStop() {
        this.doStop = true;
    }
    private synchronized boolean keepRunning() {
        return this.doStop == false;
    }

    @Override
    public void run() {
        while(keepRunning()) {
            // keep doing what this thread should do.
            System.out.println("Running");
            try {
                Thread.sleep(3L * 1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

注意doStop()和keepprunning()方法。doStop()旨在从另一个线程调用,而不是执行MyRunnable的线程,keprunning()方法由执行MyRunnable的run()方法的线程内部调用。只要doStop()没有被调用,keprunning()方法将返回true,这意味着执行run()方法的线程将继续运行。
下面是一个启动执行上述MyRunnable类实例的Java线程,过了一会儿又停止了的示例:

public class MyRunnableMain {

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        try {
            Thread.sleep(10L * 1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myRunnable.doStop();
    }
}

这个例子首先创建了一个MyRunnable实例。然后将该实例传递给一个线程并启动该线程,接着执行main()方法的线程(主线程)休眠10秒,然后调用MyRunnable实例的doStop()方法。由于在调用doStop()之后,keepRunning()将返回false,这将导致执行MyRunnable方法的线程停止。请记住,如果你的Runnable实现需要的不仅仅是run()方法(例如,stop()或pause()方法),那么你就不能再用Java lambda表达式创建你的Runnable实现了。一个Java lambda只能实现一个方法。你必须使用一个自定义类,或者一个自定义的扩展具有额外方法的Runnable的接口,并由一个匿名类实现。

下一章:竞态条件和临界区
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值