java并发编程(一)——线程的创建
前言
Java语言内置了多线程支持:一个Java程序实际上是一个JVM进程,JVM进程用一个主线程来执行main()
方法,在main()
方法内部,我们又可以启动多个线程。此外,JVM还有负责垃圾回收的其他工作线程等。
和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。例如,播放电影时,就必须由一个线程播放视频,另一个线程播放音频,两个线程需要协调运行,否则画面和声音就不同步。因此,多线程编程的复杂度高,调试更困难。
本节我们来学习Java中创建多线程的几种方式。
方式一 继承Thread
继承Thread,重写里面的run()方法,也就是我们要实现的代码逻辑:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello world");
}
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
myThread1.start();
}
}
启动线程时调用Thread类的start()方法即可。
方式二 实现Runable接口
实现Runable接口,并重写里面的run()方法,即需要实现的代码逻辑
public class RunableThread implements Runnable{
@Override
public void run() {
System.out.println("hello world");
}
public static void main(String[] args) {
Thread newThread=new Thread(new RunableThread());
newThread.start();
}
}
同样的,将实现了run()方法的实例传到Thread类中就可以实现多线程,调用start()方法启动线程。
方式三 使用lambda
lambda表达式来简化创建线程的过程:
public static void main(String[] args) {
new Thread(()->{
System.out.println("hello world!!");
}).start();
}
或者使用匿名内部类的方式:
/** *描述:匿名内部类创建线程 */
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
不管事lambda表达式还是匿名内部类,它们仅仅是在语法层面上实现了线程,并不能把它归结于实现多线程的方式
方式四 使用线程池
使用线程池来创建线程举例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RunableThread implements Runnable{
@Override
public void run() {
System.out.println("hello world");
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
executorService.submit(new RunableThread());
}
executorService.shutdown();
}
}
使用线程池创建线程时也得传入实现了Runable接口的类,重写里面的run()方法来实现逻辑。
启动线程时使用submit方法将线程添加到线程池中。
我们来解线程池的源码,
可以看到线程池本质上还是通过new Thread()方式来创建线程的,只不过是创建线程的时候指定了一些默认值,比如线程名字、是否是守护线程、线程优先级等等。
方式五 有返回值的 Callable
import java.util.concurrent.*;
public class Task implements Callable<String> {
@Override
public String call() throws Exception {
return "hello!**";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
//定义一个任务
Callable<String> task= new Task();
//提交任务并获得一个future对象(一个Future类型的实例代表一个未来能获取结果的对象)
Future<String> future = executorService.submit(task);
String result = future.get();
// 从Future获取异步执行返回的结果:
System.out.println(result);
}
}
Runnable 创建线程是无返回值的,而 Callable 和与之相关的 Future、FutureTask,它们可以把线程执行的结果作为返回值返回。
无论是 Callable 还是 FutureTask,它们首先和 Runnable 一样,都是一个任务,是需要被执行的,而不是说它们本身就是线程。它们可以放到线程池中执行,submit()方法将任务放到线程池中,并由线程池创建线程,最终都是靠线程来执行的,而子线程的创建最终都脱离不了最初的两种基本方式,也就是实现 Runnable 接口和继承 Thread 类。
实现 Runnable 接口比继承 Thread 类实现线程要好
实现 Runnable 接口和继承 Thread 类实现线程,两者本质上是一种方式,都是构造一个Thread类,这是创建线程的唯一方式。
推荐使用实现Runnable接口的方式:
原因有三:
- 代码架构方面 Runnable接口中只有一个run()方法,定义了需要执行的内容。从代码架构考虑,Runnable负责执行内容,Thread类负责线程启动和属性设置等内容,权责分明。
- 性能方面 使用继承Thread类方式,每次执行任务都需要新建一个独立的线程,执行完任务后销毁线程,如果还想执行这个任务,就必须再新建一个继承Thread的类,如果此时执行的内容比较少,比如只是在 run() 方法里简单打印一行文字,那么它所带来的开销并不大,相比于整个线程从开始创建到执行完毕被销毁,这一系列的操作比 run() 方法打印文字本身带来的开销要大得多,相当于捡了芝麻丢了西瓜,得不偿失。如果我们使用实现 Runnable 接口的方式,就可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次新建销毁线程,大大降低了性能开销。
- 可扩展性方面 Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。
总结
- Java中创建多线程的方式有Runnable 接口和继承 Thread 类等好几种,但本质上只有一种,即构造Thread类
- 实现 Runnable 接口比继承 Thread 类实现线程好
我的博客
欢迎访问我的博客:易水寒博客
还没有评论,来说两句吧...