Java学习-----如何创建线程
在多线程编程中,创建线程是实现并发操作的基础。不同的编程语言和开发环境提供了多种创建线程的方式,每种方式都有其独特的原理、作用、优缺点。下面将详细介绍几种常见的创建线程的方式。
(一)继承 Thread 类
在 Java 中,Thread 类是线程操作的核心类,继承 Thread 类创建线程是一种基础方式。通过创建一个继承 Thread 类的子类,并重写其 run () 方法,然后实例化该子类对象,调用 start () 方法即可启动线程。
Thread 类实现了 Runnable 接口,其中 run () 方法是线程的执行体。当调用 start () 方法时,Java 虚拟机会为该线程分配 CPU 资源等,使其进入就绪状态,当得到 CPU 时间片后,就会执行 run () 方法中的代码。这种方式可以方便地创建线程,通过重写 run () 方法定义线程要执行的任务,实现线程的并发执行。
Thread的优点主要有:实现简单,直接继承 Thread 类,重写 run () 方法即可,无需额外实现接口。
而缺点则主要有:Java 是单继承机制,一个类继承了 Thread 类后,就不能再继承其他类,灵活性较差。
下面用一个案例简单的实现一下该继承方式:
class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("线程" + Thread.currentThread().getName() + "执行:" + i);}}
}public class ThreadTest {public static void main(String[] args) {MyThread thread = new MyThread();thread.setName("线程1");thread.start();}
}
在上述代码中,MyThread 类继承了 Thread 类,并重写了 run () 方法,在 run () 方法中定义了线程要执行的循环打印任务。在 main 方法中,创建了 MyThread 类的实例,设置线程名称后调用 start () 方法启动线程,线程启动后会执行 run () 方法中的内容。
(二)实现 Runnable 接口
实现 Runnable 接口创建线程,是让一个类实现 Runnable 接口,并重写其 run () 方法,然后将该类的实例作为参数传递给 Thread 类的构造方法,最后调用 Thread 对象的 start () 方法启动线程。
Runnable 接口中只包含一个 run () 方法,它定义了线程要执行的任务。当将实现了 Runnable 接口的对象传递给 Thread 类后,Thread 类的 run () 方法会调用该对象的 run () 方法,从而实现线程的执行。这种方式可以让多个线程共享同一个实现了 Runnable 接口的对象,从而共享资源,提高资源的利用率。
其优缺点主要为:
优点:一个类可以同时实现多个接口,不影响其继承其他类,灵活性高;多个线程可以共享一个 Runnable 实例,适合多个线程处理同一份资源的场景。
缺点:相对于继承 Thread 类,实现过程稍显复杂,需要多一步将 Runnable 实例传递给 Thread 类的操作。
还是一样,用一个简单的案例来实现一下这个方法:
class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("线程" + Thread.currentThread().getName() + "执行:" + i);}}
}public class RunnableTest {public static void main(String[] args) {MyRunnable runnable = new MyRunnable();Thread thread1 = new Thread(runnable, "线程1");Thread thread2 = new Thread(runnable, "线程2");thread1.start();thread2.start();}
}
上述代码中,MyRunnable 类实现了 Runnable 接口,重写了 run () 方法。在 main 方法中,创建了 MyRunnable 实例,然后将其作为参数分别传递给两个 Thread 对象,设置不同的线程名称后启动线程。两个线程会共享 MyRunnable 实例,共同执行 run () 方法中的任务。
(三)实现 Callable 接口
Callable 接口是 Java 5 中引入的,与 Runnable 接口类似,但它可以返回线程执行的结果,并且可以抛出异常。实现 Callable 接口创建线程时,需要与 FutureTask 类配合使用。
Callable 接口中的 call () 方法是线程的执行体,该方法有返回值且可以抛出异常。FutureTask 类实现了 Future 接口和 Runnable 接口,它可以接收 Callable 对象作为参数。当将 FutureTask 对象传递给 Thread 类并启动线程后,线程会执行 call () 方法,执行结果会被 FutureTask 保存,通过 FutureTask 的 get () 方法可以获取该结果。这种方式适合需要获取线程执行结果的场景,能够方便地处理线程执行过程中可能出现的异常。
Callable优缺点主要有
优点:可以获取线程执行结果,能抛出异常,功能更强大;同样遵循面向接口编程思想,不影响类的继承,灵活性高。
缺点:实现过程相对复杂,需要结合 FutureTask 类使用,获取结果的 get () 方法可能会阻塞当前线程。
下面是其的一个简单案例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= 5; i++) {sum += i;System.out.println("线程" + Thread.currentThread().getName() + "执行:" + i);}return sum;}
}public class CallableTest {public static void main(String[] args) {MyCallable callable = new MyCallable();FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread thread = new Thread(futureTask, "线程1");thread.start();try {int result = futureTask.get();System.out.println("线程执行结果:" + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}
}
在该案例中,MyCallable 类实现了 Callable接口,重写了 call () 方法,该方法计算 1 到 5 的和并返回。创建 FutureTask 对象时将 MyCallable 实例传入,再将 FutureTask 对象作为参数创建 Thread 对象并启动线程。通过 futureTask.get () 方法可以获取 call () 方法的返回结果,该方法会阻塞当前线程直到获取到结果。
(四)使用线程池
线程池是一种线程管理机制,它预先创建一定数量的线程,当有任务需要执行时,从线程池中取出一个线程来执行任务,任务执行完成后,线程不会被销毁,而是返回到线程池中等待下一个任务。使用线程池创建线程,其实是从线程池中获取线程来执行任务。
线程池内部维护了一个线程队列和任务队列。当提交任务时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,且当前线程数量未达到线程池的最大容量,则创建新线程执行任务;如果达到最大容量,则将任务放入任务队列等待,当有线程空闲时,再从任务队列中取出任务执行。线程池可以减少线程的创建和销毁带来的开销,提高系统的性能和资源利用率,同时可以对线程进行统一管理和控制。
线程池的优缺点主要有:
优点:降低资源消耗,提高响应速度,便于线程管理(如控制最大并发数等)。
缺点:线程池的参数设置需要根据实际情况进行调整,设置不当可能会影响性能;对于一些简单的任务,使用线程池可能会增加一定的复杂性。
下面是一个简单的线程池实现:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;class MyTask implements Runnable {private int taskId;public MyTask(int taskId) {this.taskId = taskId;}@Overridepublic void run() {System.out.println("任务" + taskId + "由线程" + Thread.currentThread().getName() + "执行");}
}public class ThreadPoolTest {public static void main(String[] args) {// 创建一个固定大小为3的线程池ExecutorService executorService = Executors.newFixedThreadPool(3);for (int i = 0; i < 5; i++) {executorService.submit(new MyTask(i));}// 关闭线程池executorService.shutdown();}
}
在上面的代码中,我们通过 Executors.newFixedThreadPool (3) 创建了一个固定大小为 3 的线程池。然后循环提交 5 个 MyTask 任务到线程池,线程池会从内部的线程中分配线程来执行这些任务。任务执行完成后,线程会返回到线程池中。最后调用 shutdown () 方法关闭线程池。