如何在程序中创建出多条线程

多线程是编程中的一个重要概念,它允许程序同时执行多个任务,每个任务可以看作是一个线程。在Java中,多线程尤为常见且强大,它通过允许程序在并发环境下运行,提高了程序的执行效率和响应速度。以下是对Java多线程的详细讲解:

基本概念

  1. 线程(Thread):线程是进程中的实体,是CPU调度和分派的基本单位,它是比进程更小的独立运行的单位。线程一般不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
  2. 进程(Process):进程是程序的一次动态执行过程,是程序代码、数据和相关资源的集合。一个进程可以拥有多个线程,这些线程共享进程的地址空间和系统资源。

Thread 类

在Java中,Thread 类是处理多线程的核心类。每个线程都是通过 Thread 类的一个实例来表示的。Java 允许你继承 Thread 类来创建新的线程类,或者实现 Runnable 接口。下面我将详细解释 Thread 类中的几个关键方法。

Thread类的常用方法

  1. public void run()

    线程的任务方法。当线程启动时,会自动调用此方法。在继承Thread类创建线程时,通常需要重写此方法以定义线程的具体任务。
  2. public void start()

    启动线程。调用此方法后,线程会进入就绪状态,等待CPU调度执行。注意,直接调用run()方法并不会启动新线程,而是像普通方法一样在当前线程中执行。
  3. public String getName()

    获取当前线程的名称。默认情况下,线程的名称是"Thread-索引",其中索引是一个递增的整数。
  4. public void setName(String name)

    为线程设置名称。通过此方法可以自定义线程的名称,便于在调试和日志记录中识别不同的线程。
  5. public static Thread currentThread()

    获取当前执行的线程对象。此方法允许在代码中获取当前正在执行的线程实例,进而可以调用该线程的方法或属性。
  6. public static void sleep(long time)

    让当前执行的线程休眠指定的毫秒数后,再继续执行。这是一个静态方法,用于暂停当前线程的执行,让出CPU资源给其他线程。
  7. public final void join()

    让调用当前这个方法的线程先执行完。这个方法的作用是等待调用它的线程(即当前线程)终止。在join()方法返回之前,其他线程(即调用join()方法的线程)无法继续执行。

Thread类的常见构造器

  1. public Thread(String name)

    可以为当前线程指定名称。通过构造器中的name参数,可以为线程设置一个易于识别的名称。
  2. public Thread(Runnable target)

    封装Runnable对象成为线程对象。这种方式是实现多线程的另一种途径,即实现Runnable接口。通过这种方式,可以将线程的任务与线程本身分离,使得代码更加灵活。
  3. public Thread(Runnable target, String name)

    封装Runnable对象成为线程对象,并指定线程名称。这个构造器结合了上述两种构造器的功能,既可以将线程的任务与线程本身分离,又可以自定义线程的名称。

创建方式一

通过继承Thread类来创建线程

步骤 1: 定义子类继承Thread

首先,你需要定义一个子类来继承Java的java.lang.Thread类。在这个子类中,你需要重写run方法。run方法是线程启动时执行的代码块。

步骤 2: 创建MyThread类的对象

接着,你需要创建MyThread类的一个或多个对象。这些对象就是线程实例。

步骤 3: 调用线程对象的start方法启动线程

最后,你需要调用线程对象的start方法来启动线程。调用start方法会启动一个新的线程,并让这个线程执行其run方法中的代码。注意,不要直接调用run方法,这样会导致代码在调用它的线程(通常是主线程)中顺序执行,而不会创建新的线程。

为了举一个更明显的例子来说明通过继承Thread类来创建线程,我们可以考虑一个简单的场景:有两个线程,一个负责打印奇数,另一个负责打印偶数。我们将分别创建两个类(OddThreadEvenThread)来继承Thread类,并在它们的run方法中实现打印奇数或偶数的逻辑。

// OddThread 类,继承自 Thread 类,用于打印奇数  
class OddThread extends Thread {  
    private int start;  
    private int end;  
  
    public OddThread(int start, int end) {  
        this.start = start;  
        this.end = end;  
    }  
  
    @Override  
    public void run() {  
        for (int i = start; i <= end; i += 2) {  
            System.out.println(Thread.currentThread().getName() + " 打印奇数: " + i);  
            try {  
                // 为了更明显地看到线程切换,可以添加一些延迟  
                Thread.sleep(100);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}  
  
// EvenThread 类,继承自 Thread 类,用于打印偶数  
class EvenThread extends Thread {  
    private int start;  
    private int end;  
  
    public EvenThread(int start, int end) {  
        this.start = start;  
        if (end % 2 == 0) {  
            this.end = end;  
        } else {  
            this.end = end - 1; // 确保结束值是偶数  
        }  
    }  
  
    @Override  
    public void run() {  
        for (int i = start; i <= end; i += 2) {  
            System.out.println(Thread.currentThread().getName() + " 打印偶数: " + i);  
            try {  
                // 为了更明显地看到线程切换,可以添加一些延迟  
                Thread.sleep(100);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}  
  
// 主类  
public class ThreadExample {  
    public static void main(String[] args) {  
        // 创建并启动打印奇数的线程  
        OddThread oddThread = new OddThread(1, 10);  
        oddThread.start();  
  
        // 创建并启动打印偶数的线程  
        EvenThread evenThread = new EvenThread(2, 10);  
        evenThread.start();  
  
        // 注意:主线程会继续执行,不会等待oddThread和evenThread执行完成  
    }  
}

由于线程的执行是并发的,所以输出结果的顺序可能会有所不同,这取决于JVM的线程调度策略以及操作系统的线程管理机制。

优缺点 

优点:编码简单

缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。

注意

创建方式二

通过实现Runnable接口方式 

1. 为什么使用Runnable接口?

在Java中,创建线程主要有两种方式:继承Thread类和实现Runnable接口。虽然继承Thread类是一种直观的方式,但它限制了类的继承体系(因为Java不支持多重继承)。相比之下,实现Runnable接口更为灵活,因为它允许你的类继承自其他类,并且仍然可以拥有多线程的能力。

2. Runnable接口简介

Runnable是一个函数式接口(从Java 8开始),它只定义了一个方法:run()。当你创建了一个实现了Runnable接口的类的实例后,你可以将这个实例作为参数传递给Thread类的构造函数,从而创建一个新的线程。

线程创建的基本步骤(使用Runnable接口)

①定义Runnable实现类
首先,你需要定义一个类来实现Runnable接口。实现接口意味着你必须提供run方法的实现。这个run方法将包含线程执行时所需的所有代码。

class MyTask implements Runnable {  
    @Override  
    public void run() {  
        // 在这里编写线程的任务代码  
        System.out.println(Thread.currentThread().getName() + " is running.");  
        // 假设这里有一些耗时的操作或逻辑处理  
    }  
}

②创建Runnable实现类的实例
一旦你定义了Runnable实现类,就可以创建这个类的实例了。这个实例将作为线程执行的任务。 

Runnable myTask = new MyTask();

③将Runnable实例传递给Thread构造函数
接下来,你需要将Runnable实例作为参数传递给Thread类的构造函数。这个构造函数会创建一个新的Thread对象,这个对象封装了Runnable实现类的实例。

Thread myThread = new Thread(myTask, "MyCustomThread");

注意,这里的第二个参数是可选的,用于指定线程的名称。如果省略,线程将使用默认名称。

启动线程
最后,你需要调用线程的start方法来启动线程。调用start方法会导致JVM调用Runnable实现类的run方法,在新的线程中执行。

myThread.start();

重要的是要区分start方法和run方法。start方法用于启动线程,而run方法则定义了线程执行的任务。如果你直接调用run方法(如myTask.run()),那么run方法中的代码将在当前线程中同步执行,而不是在新的线程中。

完整示例

将上述步骤组合起来,我们得到以下完整的示例:

class MyTask implements Runnable {  
    @Override  
    public void run() {  
        for (int i = 0; i < 5; i++) {  
            System.out.println(Thread.currentThread().getName() + " is running, iteration " + i);  
            try {  
                // 模拟耗时操作  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}  
  
public class Main {  
    public static void main(String[] args) {  
        Runnable myTask = new MyTask();  
          
        // 创建并启动第一个线程  
        Thread thread1 = new Thread(myTask, "Thread-1");  
        thread1.start();  
          
        // 创建并启动第二个线程(注意:这里可以创建任意数量的线程)  
        Thread thread2 = new Thread(myTask, "Thread-2");  
        thread2.start();  
          
        // main线程继续执行其他任务或结束  
        System.out.println("Main thread is ending.");  
    }  
}

 在这个示例中,我们创建了两个线程(thread1thread2),它们都执行相同的任务(由MyTask类的run方法定义)。这两个线程将并发执行,各自打印出自己的迭代次数,并模拟了耗时操作(通过Thread.sleep)。同时,main线程在启动了两个线程后继续执行并结束,而不会影响或等待这两个子线程的执行。

优点

任务类只是实现接口,可以继续继承其他类、实现其它接口,扩展性强。

 

创建方式三 

通过Callable接口和FutureTask类来创建线程的方法

让我们详细讲解一下Java中通过Callable接口和FutureTask类来创建线程的方法,并以上面提到的类似代码来举例。

Callable接口

Callable接口与Runnable接口类似,都是为了被线程执行而设计的。但与Runnable不同的是,Callable可以返回值,并且它可以抛出异常。这使得Callable接口在某些场景下比Runnable更加灵活和强大。

FutureTask类

FutureTask类实现了FutureRunnable接口。它可以将CallableRunnable对象包装起来,以便有返回值的任务可以被提交给Executor执行。如果任务通过Callable包装,那么FutureTask将返回执行结果;如果通过Runnable包装,那么FutureTaskget()方法将返回null

构造器

  • public FutureTask<>(Callable<V> callable):这个构造器接受一个Callable<V>类型的参数。Callable是一个类似于Runnable的接口,但它可以返回一个结果并且可以抛出一个异常。FutureTask会将这个Callable对象封装成一个可以异步执行的任务。与Runnable不同,Callablecall方法可以有返回值,并且可以声明抛出异常。

方法

  • public V get() throws InterruptedException, ExecutionException:这个方法用于等待计算完成,并检索其结果。如果在计算完成之前调用此方法,它将会阻塞当前线程,直到计算完成。此方法会抛出两种类型的异常:
    • InterruptedException:如果当前线程在等待过程中被中断,则会抛出此异常。
    • ExecutionException:如果计算抛出异常,则会通过此异常包装并抛出。

示例代码

假设我们有一个任务,该任务需要计算一个整数的阶乘,并返回结果。我们可以使用Callable接口来定义这个任务,并使用FutureTask来包装它,以便可以提交给线程池执行。


public class FactorialCallable implements Callable<Long> {  
    private int number;  
  
    public FactorialCallable(int number) {  
        this.number = number;  
    }  
    //重写call方法
    @Override  
    public Long call() throws Exception {  
    //描述线程的任务,返回执行后的结果
        long result = 1;  
        for (int i = 1; i <= number; i++) {  
            result *= i;  
        }  
        return result;  
    }  
}  
  
public class DirectThreadExample {  
    public static void main(String[] args) {  
        // 创建 Callable 任务实例  
        FactorialCallable task = new FactorialCallable(5);  
  
        // 由于 Thread 不能直接执行 Callable,我们需要将 Callable 包装为 FutureTask  
        FutureTask<Long> futureTask = new FutureTask<>(task);  
  
        // 创建一个新的 Thread,并将 FutureTask 作为其任务  
        Thread thread = new Thread(futureTask);  
  
        // 启动线程  
        thread.start();  
  
        try {  
            // 等待任务完成并获取结果  
            Long result = futureTask.get(); // 这会阻塞当前线程直到任务完成  
            System.out.println("Factorial of 5 is: " + result);  
        } catch (InterruptedException | ExecutionException e) {  
            e.printStackTrace();  
        }  
  
        // 注意:在实际应用中,你可能需要处理线程的中断和优雅关闭等逻辑  
    }  
}

优点

线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。

可以在线程执行完毕后去获取线程执行的结果。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值