Java--线程的创建和启动
2018-02-26 00:00
197 查看
上一篇:进程和线程的基本概念
线程的创建有三种方法:继承Thread、实现Runnable接口、使用Callable和Future.
定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
创建Thread子类的实例,即创建了线程对象。
调用线程对象的start()方法来启动线程。
虽然上面程序只显式地创建了两个线程,但实际上程序有三个线程,即两个子线程和一个主线程。当Java运行时,程序至少创建一个主线程,该主线程的执行体不是由run()方法确定的,而是由main()方法确定。
使用继承Thread类的方法创建线程类时,多个线程之间无法共享线程类的实例变量。如下图所示,Thread-0线程和Thread-1线程都是从0计数开始(共享进程类的实例变量i )。
程序可以通过setName(String name)为线程设置名字,也可以通过getName()方法返回线程的名字。在默认情况下,主线程名字为main,其他线程名字依次为Thread-0、Thread-1、...、Thread-n。
currentThread()是Thread()类的静态方法,该方法返回当前正在执行的线程对象。
定义Runnable接口的实现类,并重写该接口的run()方法。
创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
调用start()方法来启动线程。
注:Java 8 增加了lambda(λ)表达式,因为Runnable接口是函数式接口,所以可以直接使用lambda表达式来创建Runnable对象。
采用Runnable接口的方式创建的多个线程可以共享线程类的实例变量。如下图所示,新线程1和新线程2是累加的关系。
Runnable对象仅仅作为Thread对象的target,Runnable实现类中的run()方法仅仅作为线程执行体。而世纪的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。正因为程序所创建的Runnable对象只是线程的target, 而多个线程可以共享同一个target,所以多个线程可以共享同一个线程类(实际上应该是线程的target类)的实例变量。
但是从Java 5开始,Java提供了Callable接口,该接口提供一个call()方法作为线程执行体,并且可以有返回值,还可以声明抛出异常。这很像Runnable接口的增强版,因此可以想出使用Callable对象作为Thread的target。问题是:Callable接口不是Runnable的自接口,因此不能直接作为Thread的target。
Java 5同时提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现了Callable接口和Runnable接口,可以作为Thread的target使用。
步骤如下:
创建Callable接口的实现类,实现call()方法,再创建Callable实现类的实例。(Callable接口也是函数式接口,可以使用lambda表达式)
使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。
使用FutureTask对象作为Thread对象的target,创建并启动线程。
调用FutureTask对象的gat方法来获得子线程执行结束后的返回值。
Future接口定义了如下几个共有方法:
boolean cancel(boolean mayInterruptIfRunning):试图取消Future里关联的Callable。
V get():返回Callable任务里call()方法的返回值。调用该方法将导致程序阻塞,必须等到子程序结束后才能得到返回值。
V get(long timeout, TimeUnit unit):返回Callable任务里call()方法的返回值。该方法让程序最多阻塞timeout和unit指定的时间,如果超出时间Callable任务依然没有返回值,抛出TimeoutException异常。
boolean isCancelled():如果在Callable任务正常完成前被取消,则返回true。
boolean isDone():如果Callable任务已完成,则返回true。
线程类只是实现了接口,还可以继承其他类;
多个线程可以共享同一个target对象,非常适合多个相同线程来处理同一份资源的情况;
劣势是,变成稍微复杂,如果想访问当前线程,需要使用Thread.currentThread()方法。
采用继承Thread类实现:
劣势是,因为继承了Thread类,无法继承其他类;
优势是,编程简单。
综上,一般推荐使用实现接口的方式来创建多线程。
线程的创建有三种方法:继承Thread、实现Runnable接口、使用Callable和Future.
1、继承Thread类创建线程类
步骤如下:定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
创建Thread子类的实例,即创建了线程对象。
调用线程对象的start()方法来启动线程。
public class FirstThread extends Thread{//继承Thread类 private int i; public void run() { for(;i<100;i++) System.out.println(getName()+" "+i); } public static void main(String[] args){ new FirstThread().start();//创建并启动第一个线程 new FirstThread().start();//创建并启动第二个线程 } }
虽然上面程序只显式地创建了两个线程,但实际上程序有三个线程,即两个子线程和一个主线程。当Java运行时,程序至少创建一个主线程,该主线程的执行体不是由run()方法确定的,而是由main()方法确定。
使用继承Thread类的方法创建线程类时,多个线程之间无法共享线程类的实例变量。如下图所示,Thread-0线程和Thread-1线程都是从0计数开始(共享进程类的实例变量i )。
程序可以通过setName(String name)为线程设置名字,也可以通过getName()方法返回线程的名字。在默认情况下,主线程名字为main,其他线程名字依次为Thread-0、Thread-1、...、Thread-n。
currentThread()是Thread()类的静态方法,该方法返回当前正在执行的线程对象。
2、实现Runnable接口创建线程类
步骤如下:定义Runnable接口的实现类,并重写该接口的run()方法。
创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
调用start()方法来启动线程。
注:Java 8 增加了lambda(λ)表达式,因为Runnable接口是函数式接口,所以可以直接使用lambda表达式来创建Runnable对象。
public class SecondThread implements Runnable{ private int i; @Override public void run() { // TODO Auto-generated method stub for(;i<100;i++) System.out.println(Thread.currentThread()+" "+i); } public static void main(String[] args){ SecondThread st = new SecondThread(); new Thread(st,"新线程1").start(); new Thread(st,"新线程2").start(); } }
采用Runnable接口的方式创建的多个线程可以共享线程类的实例变量。如下图所示,新线程1和新线程2是累加的关系。
Runnable对象仅仅作为Thread对象的target,Runnable实现类中的run()方法仅仅作为线程执行体。而世纪的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。正因为程序所创建的Runnable对象只是线程的target, 而多个线程可以共享同一个target,所以多个线程可以共享同一个线程类(实际上应该是线程的target类)的实例变量。
3、使用Callable和Future创建线程类
上面已经指出,通过Runnable接口创建线程时,Thread类的作用是把run()方法包装成线程执行体。那么可不可以直接把任意方法包装成线程执行体呢?Java目前不行。(C#可以)但是从Java 5开始,Java提供了Callable接口,该接口提供一个call()方法作为线程执行体,并且可以有返回值,还可以声明抛出异常。这很像Runnable接口的增强版,因此可以想出使用Callable对象作为Thread的target。问题是:Callable接口不是Runnable的自接口,因此不能直接作为Thread的target。
Java 5同时提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现了Callable接口和Runnable接口,可以作为Thread的target使用。
步骤如下:
创建Callable接口的实现类,实现call()方法,再创建Callable实现类的实例。(Callable接口也是函数式接口,可以使用lambda表达式)
使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。
使用FutureTask对象作为Thread对象的target,创建并启动线程。
调用FutureTask对象的gat方法来获得子线程执行结束后的返回值。
public class ThirdThread { public static void main(String[] args) { ThirdThread rt = new ThirdThread(); FutureTask<Integer> task = new FutureTask<Integer> (Callable<Integer>)()->{//lambda表达式 int i = 0; for(;i<100;i++) System.out.println(Thread.currentThread()+" "+i); }); new Thread(task,"有返回值的线程").start(); try { System.out.print("子线程返回的值"+task.get()); }catch(Exception ex) { ex.printStackTrace(); } } }
Future接口定义了如下几个共有方法:
boolean cancel(boolean mayInterruptIfRunning):试图取消Future里关联的Callable。
V get():返回Callable任务里call()方法的返回值。调用该方法将导致程序阻塞,必须等到子程序结束后才能得到返回值。
V get(long timeout, TimeUnit unit):返回Callable任务里call()方法的返回值。该方法让程序最多阻塞timeout和unit指定的时间,如果超出时间Callable任务依然没有返回值,抛出TimeoutException异常。
boolean isCancelled():如果在Callable任务正常完成前被取消,则返回true。
boolean isDone():如果Callable任务已完成,则返回true。
创建线程的三种方式对比:
采用Runnable、Callable接口实现多线程:线程类只是实现了接口,还可以继承其他类;
多个线程可以共享同一个target对象,非常适合多个相同线程来处理同一份资源的情况;
劣势是,变成稍微复杂,如果想访问当前线程,需要使用Thread.currentThread()方法。
采用继承Thread类实现:
劣势是,因为继承了Thread类,无法继承其他类;
优势是,编程简单。
综上,一般推荐使用实现接口的方式来创建多线程。
下一篇----五态模型&线程控制
相关文章推荐
- 浅析Java中线程的创建和启动
- Java线程:创建与启动
- Java线程的创建与启动(1)
- Java_多线程_创建及启动线程
- 7.创建以及启动一个Java线程
- Java线程:创建与启动
- Java总结(九)——(线程模块 一(线程的创建(方法一)与启动,线程状态与生命周期,进程与线程))
- Java基础-多线程-①线程的创建和启动
- Java线程的创建与启动(1)
- (一)线程的创建和启动--java 多线程编程的那点小事
- Java线程:创建与启动
- Java之线程的创建和启动
- java线程的创建,启动,执行
- (十)java并发编程--创建和启动线程(java.lang.Thread 、java.lang.Runnable)
- java 线程 启动 和 创建
- Java线程创建和启动
- Java并发1——线程创建、启动、生命周期与线程控制
- Java线程的三种三种创建和启动
- Java线程:创建与启动
- Java线程:创建与启动