线程是进程组成部分,一个进程可以有多个线程,一个线程必须有一个父进程。线程的优点:多个线程共享内存,并且可以高并发,多个线程共享一个进程的虚拟空间。线程有这么多好处,那么他的创建方法有三种方式:
方式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()方法。