在Java中,可以通过三种方式来实现多线程:
- 第一种继承Thread类,重写run()方法
- 第二种实现Runnable接口,重写run()方法
- 第三种实现Callable接口,重写call()方法,并使用Future来获取call()方法的返回结果
1. Thread类实现多线程
使用Thread类实现多线程的步骤如下:
(1) 创建一个Thread线程类的子类(子线程),同时重写Thread类的run()方法
(2) 创建该子类的实例对象,并通过调用start()方法启动线程
class MyThread1 extends Thread{
public MyThread1(String name){
super(name);
}
@Override
public void run() {
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+"的run方法在运行");
}
}
}
public class test {
public static void main(String[] args) {
MyThread1 thead1 = new MyThread1("thead1");
thead1.start();
MyThread1 thead2 = new MyThread1("thead2");
thead2.start();
}
}
从程序结果可以看出,创建的两个线程实例交互运行,但实际上,该程序中还有一个main()方法开启的主线程,这是程序的入口,仅用于创建并启动了两个子线程实例,并没有执行其他动作输出。。
注: 通过继承thread类的方式实现了多线程,但这种方式有一定的局限性,因为Java中只支持类的单继承,如果某个类已经继承了其他父类,就无法再继承thread类来实现多线程。。
2.Runnable接口实现多线程
使用Runnable接口的方法实现多线程的步骤:
(1)创建一个Runnable接口的实现类,同时重写接口中的run()方法
(2)创建Runnable接口的实现类对象
(3)使用Thread有参构造方法创造线程实例,并将Runnable接口的实现类的实例对象作为参数传入。
(4)调用线程实例的start()方法启动线程
class MyThread implements Runnable{
@Override
public void run() {
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+"的run方法在运行");
}
}
}
public class test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//使用public Thread(Runnable target, String name){}构造方法创建线程对象
Thread thread1 = new Thread(myThread,"thread1");
thread1.start();
Thread thread2 = new Thread(myThread,"thread2");
thread2.start();
}
}
提示: Runnable接口中只有一个抽象的run()方法,那么该接口就属于Java JDK8中定义的函数式接口,在使用时,可以直接通过Lambda表达式的方式更简洁的实现线程实例。。
3.callable接口实现多线程
通过Thead类和Runnable接口实现多线程时,需要重写Run()方法,但是由于该方法没有返回值,因此无法从多个线程获取返回值。Java提供了一个Callable()接口,来满足这种既能创建多线程又可以有返回值的需求。。
实现Callable接口创建并启用多线程的步骤:
(1)创建一个Callable接口的实现类,同时重写Callable接口的call()方法
(2)创建Callable接口的实现类接口
(3)通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象
(4)使用参数FutureTask类对象的Thread有参构造方法创建Thread线程实例
(5)通过调用线程实例的start()方法启动线程
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<Object> {
@Override
public Object call() throws Exception {
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+"的run方法在运行");
}
return i;
}
}
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask<Object> objectFutureTask1 = new FutureTask<Object>(myThread);
Thread thread1 = new Thread(objectFutureTask1,"thread1");
thread1.start();
FutureTask<Object> objectFutureTask2 = new FutureTask<Object>(myThread);
Thread thread2 = new Thread(objectFutureTask2,"thread2");
thread2.start();
System.out.println("thread1返回结果:"+objectFutureTask1.get());
System.out.println("thread2返回结果:"+objectFutureTask2.get());
}
}
提示: callable接口中只有一个抽象的run()方法,那么该接口就属于Java JDK8中定义的函数式接口,在使用时,可以直接通过Lambda表达式的方式更简洁的实现线程实例。。
Callable接口方式实现的多线程是通过FutureTask类来封装和管理返回结果的,该类的直接父接口是RunnableFuture,从名称上可以看出RunnableFuture是由Runnable和Future组合的结合体,如下是FutureTask类的继承关系。。
Future接口的方法:
方法申明 | 功能描述 |
---|---|
boolean cancel(boolean mayInterruptIfRunning) | 用于取消任务,参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置为true,则表示可以取消正在执行的任务 |
boolean isCancelled() | 判断任务是否被取消,如果任务正常完成前被取消完成,则返回true |
boolean isDone() | 判断任务是否已经完成,若已经完成,则返回true |
V get() | 用于获取执行结果,这个方法会发生阻塞,一直等到任务执行完毕才返回执行结果 |
V get(long timeout, TimeUnit unit) | 用于在指定的时间内获取执行结果,如果在指定的时间内,还没有获取到结果,就直接返回null |
4.三种实现多线程方式的对比
下面是模拟列车发100票,用不同线程的方法进行实现:
class myThread extends Thread{
private int tickets=100;
@Override
public void run() {
while(true){
if(tickets>0){
System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
}
}
}
}
public class test {
public static void main(String[] args) {
new myThread().start();
new myThread().start();
new myThread().start();
new myThread().start();
}
}
从图中可以看出,每张票被发售了4次。出现这种现象的原因是4个线程没有共享100张票,而是各自出售了100张票。在系统中创建了4个myThread()的对象,就等于创建了4个售票程序,而每个myThread对象中都有一个tickets变量,这样每个线程在执行程序任务时都会独立的处理各自的资源,而不是共同处理同一个 售票资源。。
注: 在上述程序中,启动4个线程并没有指定线程的名称,但是结果显示有线程的名称,这是因为,在创建多线程时如果没有通过构造方法指定线程的名称,则系统默认生成线程的名称。。
class myThread implements Runnable{
private int tickets=100;
@Override
public void run() {
while(true){
if(tickets>0){
System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
}
}
}
}
public class test {
public static void main(String[] args) {
myThread myThread = new myThread();
new Thread(myThread,"窗口1").start();
new Thread(myThread,"窗口2").start();
new Thread(myThread,"窗口3").start();
new Thread(myThread,"窗口4").start();
}
}
可以从截图中可以看出,通过实现Runnable接口只创建了一个myThread对象,然后创建了4个线程,在每个线程上都调用同一个myThread对象中的run()方法,这样就确保了4个线程同时访问一个tickets变量,共享100张票。
三种多线程的对比:
(1)Callable接口中的方法有返回值,并且可以申明抛出异常
(2)通过实现Runnable接口(Callable()接口)相对继承Thread实现多线程,其优点在:
-
① 适合多个线程去处理同一个共享资源的情况,把线程同程序代码,数据有效的分离,很好的体现了面向对象的设计思想。
-
② 可以避免Java单继承带来的局限性。由于一个类不能同时有两个父类,所以在当前类已经有一个父类的基础上,那么就只能采用实现Runnable接口(Callable()接口)的方法实现多线程。
在实际开发中,大部分线程都会采用实现Runnable接口(Callable()接口)的方式实现多线程。。
5.后台进程
在上述的售票代码中,当main()方法中创建并启动的4个线程代码执行完毕后,主线程也就随之结束了。 通过程序的运行可以看出,虽然主线程结束了,但整个Java程序并没有随之结束,仍然在执行售票的代码,即没有下列代码的返回。。
对Java程序 来说,只要还有一个前台进程在运行,这个进程就不会结束,如果一个进程只在后台线程运行,这个进程就会结束。
前台进程和后台进程只是一种相对的概念,新创建的线程默认都是前台线程如果某个线程在启动之前调用了setDaemon(true)语句,这个线程就会变成一个后台进程。
class myThread implements Runnable{
@Override
public void run() {
while(true){
System.out.println(Thread.currentThread().getName()+"--在运行");
}
}
}
public class test {
public static void main(String[] args) {
System.out.println("main线程是后台线程吗??"+Thread.currentThread().isDaemon());
myThread myThread = new myThread();
Thread thread = new Thread(myThread, "后台进程");
System.out.println("thread线程默认是后台进程吗??"+thread.isDaemon());
thread.setDaemon(true);
System.out.println("thread线程是后台进程吗??"+thread.isDaemon());
thread.start();
for(int i=0;i<5;i++){
System.out.println(i);
}
}
}
注: 要将某个线程设置为后台进程,必须在该线程启动之前,即setDaemon(true)必须在start()方法之前调用,否则后台线程设置无效。。