线程是进程组成部分,一个进程可以有多个线程,一个线程必须有一个父进程。线程的优点:多个线程共享内存,并且可以高并发,多个线程共享一个进程的虚拟空间。线程有这么多好处,那么他的创建方法有三种方式:
方式1:通过继承Thread类来创建线程。
具体步骤:
a.定义Thread类的子类,并重写该类的run()方法。run()方法的方法体是线程需要完成的任务,称为线程执行体。
b.创建Thread子类的实例。
c.调用线程对象的start()方法来开启线程。
public class FirstThread extends Thread { /*创建的线程不能共用这个实例变量,每次新创建的线程对象的实例变量是不同。*/ private Integer i=0 ; @Override public void run(){ for(;i<10;i++){ System.out.println(this.getName()+"\t"+i); } } }通过继承Thread类的实例,可以通过this来得到当前线程信息。
具体调用:
public class FirstThreadTest { public static void main(String[] args) { // TODO Auto-generated method stub for(int i = 0 ;i < 10 ;i ++){ System.out.println(Thread.currentThread().getName()+"\t"+i); if(i==5){ new FirstThread().start(); new FirstThread().start(); } } } }其中,运行的线程有三个:主线程 - main方法-主线程执行体 , 新创建的两个线程 new FirstThread() - run方法-线程执行体。
使用继承Thread类来创建线程对象的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
方式2:通过实现Runnable接口实现类来创建线程。
步骤:
a.定义Runnable接口的实现类,必须重写接口中的run()方法,作为线程执行体。
b.创建Runnable接口实现类的实例,并以此作为Thread的target来创建Thread对象,该对象才是真正的Thread对象。
c.调用线程对象的start()方法来启动线程。
public class SecondThread implements Runnable { private Integer i; @Override public void run() { // TODO Auto-generated method stub for(i = 0 ;i <10 ; i++){ System.out.println(Thread.currentThread().getName()+"\t"+i); } } }通过Runnable接口实现类创建的对象,必须通过Thread.currentThread()方法来得到当前线程对象。
public class SecondThreadTest { public static void main(String[] args) { SecondThread st = new SecondThread(); for(int i = 0;i<10;i++){ if(i==5){ new Thread(st,"线程1").start(); new Thread(st,"线程2").start(); } System.out.println(Thread.currentThread().getName()+"\t"+i); } } }通过运行该程序,可以发现两个子线程的i值是连续的,说明该方式创建的线程可以共享线程类的实例变量。原因是因为:实现类的实例只是线程的target,而多个线程可以共享同一个target。
方式3:使用Callable和Future创建线程。(JAVA5之后的版本可以,JAVA8之后的版本可以通过Lambda表达式来简化步骤)
使用原因:通过实现Runnable接口创建多线程时,Thread类的作用就是将run()方法包装成线程执行体。Callable接口类似于Runnable接口的增强版,提供了一个call()方法作为线程执行体,但是run()方法优势在于可以有返回值和抛出异常。但是由于Callable实现类的实例有返回值,是新增接口,不能直接作为Thread类的target,所以通过Future接口提供类一个FutureTask实现类的实例来作为Thread类的target。
步骤:
a.创建Callable接口的实现类,并实现Call()方法,作为线程执行体,且有返回值,再创建Callable实现类的实例。
b.使用futureTask类来包装Callable对象,直接封装了Callable对象的call()方法的返回值。
c.使用FutureTask对象作为Thread对象的target创建并启动线程。
d.调用FutureTask对象的get()方法来获取子线程执行结束后的返回值。
public class ThirdThread implements Callable<Integer> { @Override public Integer call() throws Exception { int i = 0 ; for(;i<10;i++){ System.out.println(Thread.currentThread().getName()+"\t"+i+"子线程执行体"); } return i; } }调用:
public class ThirdThreadTest { public static void main(String[] args){ FutureTask<Integer> ft = new FutureTask<Integer>(new ThirdThread()); for(int i = 0 ;i <10 ;i++){ System.out.println(Thread.currentThread().getName()+"的循环变量i是"+i); if(i==5){ new Thread(ft,"有返回值的线程").start(); } } try { System.out.println("子线程的返回值:"+ft.get()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }以上两种类型三种方式的优缺点:
继承Thread类的方式:
优点:步骤简单,如需访问当前线程,可以直接用this来调用。
缺点:无法继承其他类。
实现Runnable接口或者Callable/Future接口的方式:
优点:多个线程可以共享一个target对象,所以适合用于多个线程处理同一个资源的情况,从而将CPU、数据、代码分开,形成清晰的模型。
缺点:编程复杂,若想访问当前线程只能通过Thread.currentThread()方法。
