java--线程的构建(来源疯狂java讲义)
首先在这里先介绍一下线程和进程的区别:
进程是一个程序一次运行的状态,进程是系统进行资源分配和调度的一个独立的单位。
进程包括三个特征:
独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己的私有地址空间,在没有经过进程本身允许的情况下,一个用户的进程不可以直接访问其他进程的地址空间。
动态性:进程和程序的区别在于,程序只是一个静态的指令集合。而进程是一个正在系统中活动的指令集合。在进程中加入了时间概念。进程具有自己的生命周期和各种不同的状态。这些概念在程序中都是不具备的。
并发性:多个进程可以在单个处理器上并发执行。多个进程之间不会互相影响。
并发性和并行性:这是两个不同的概念,并行性是指在同一时刻,有多条指令在多个处理器上同时执行,并发是指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
其次,我们介绍一下线程的创建和启动:
线程的创建有:继承Thread类、实现Runnable接口、使用Callable和Future创建线程
1、继承Thread类:
它在java.lang.Thread
原形为:
Public class Thread extends Object implements Runnable{}
它实现了Runnable 接口。
通过继承Thread类创建线程类步骤入下:
(1)、定义Thread类的子类,并重写run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
(2)创建Thread子类的实例,即创建线程对象。
(3)调用线程对象的start()方法来启动该线程。
Start()方法,就是提醒一下该线程该运转了,然后立刻读取start()下面的代码。
下面列举一个通过继承Thread类来创建线程类。
1 Public class FistThread extends Thread{ 2 3 Private int i; 4 5 //重写run 方法,run方法的方法体就是线程的执行体 6 7 Public void run(){ 8 9 For ( ; i<100 ; i++){ 10 11 //当线程类继承Thread类时,直接使用this即可以获取当前线程 12 13 //Thread对象的getName()返回当先线程的名字 14 15 //因此可以直接调用getName()方法返回当前线程的名字 16 17 System.out.println ( getName() +” ”+i); 18 19 } 20 21 } 22 23 Public static void main (String [] args){ 24 25 For(int i=0; i<100 ;i++){ 26 27 //调用Thread的currentThread()方法获取当前线程 28 29 System.out.println(Thread.currentThread().getName()+ “ ”+i); 30 31 If(i==20){ 32 33 //创建启动第一个线程 34 35 new FirstThread().start(); 36 37 //创建启动第二个线程 38 39 new FirstThread().start(); 40 41 } 42 43 } 44 45 } 46 47 }
使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
就上面的例子,相当于创建了两个线程,每个线程都能够运行100次。
2、实现Runnable 接口创建线程类:
实现Runnable接口来创建并启动多线程步骤如下:
(1)、定义Runnable接口的实现类,并重写该类接口的run方法,该run方法的方法体同样是该线程的执行体。
(2)、创建Runnable实现类的实例,并以此例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)、调用线程对象start()方法来启动该线程。
下面列举一个通过实现Runnable接口创建线程类。
1 Public class SecondThread implements Runnable{ 2 3 Private int i; 4 5 Public void run (){ 6 7 For( ; i<100;i++){ 8 9 //当线程类实现Runnable接口时 10 11 //如果想获得当前线程,只能用Thread.currentThread()方法 12 13 System.out.println(Thread.currentThread().getName() +” ”+i); 14 15 } 16 17 } 18 19 Public static void main(String [] args){ 20 21 For (int i=0;i<100;i++){ 22 23 System.out.println(Thread.currentThread().getName()+” ”+i); 24 25 If(i==20){ 26 27 SecondThread st =new SecondThread(); 28 29 //通过new Thread(target,name)方法创建新线程 30 31 New Thread (st,”新线程1”).start(); 32 33 New Thread(st,”新线程2”).start(); 34 35 } 36 37 } 38 39 } 40 41 }
运行结果可以看出去两个线程i变量是连续的,也就是采用Runnable 接口的方式创建多个线程可以共享线程类的实例属性。
3、使用Callable 和 Future 创建线程
首先为什么出现这种方法。先看看前两个方法创建线程的缺点。
通过实现 Runnable 接口创建多线程时,Thread 类的作用就是把run方法包装成线程执行体,那么是否可以直接把任何方法包装成线程执行体?Java 目前是不行的。
Java 5 开始,Java 提供了Callable 接口,该接口是Runnable 的增强版,Callable 接口提供了一个call方法,该方法可以有返回值 也可以声明抛出异常。
注意:Callable 接口有泛型限制,Callable 接口里的泛型参数类型与call方法返回值类型相同。
创建并启动有返回值得线程的步骤如下:
(1)、创建Callable 接口的实现类,并实现call()方法,该call方法作为线程执行体,且该call方法有返回值。
(2)创建Callable实现类的实例,并使用FutureTask类来包装Callable对象,该FutureTask 对象封装了该Callable对象的call方法的返回值。
(3)使用FutureTask 对象作为Thread 对象的target 来创建并启动线程。
(4)调用FutureTask 对象的get方法来获得子线程执行结束之后的返回值。
下面程序通过实现Callable 接口来实现线程类,并启动该线程:
1 import java.util.concurrent.Callable; 2 3 import java.util.concurrent.FutureTask; 4 5 public class ThirdThread implements Callable<Integer> 6 7 { 8 9 //实现call()方法,作为线程的执行体 10 11 public Integer call(){ 12 13 int i=0; 14 15 for( ;i<20;i++){ 16 17 System.out.println(Thread.currentThread().getName()+"的循环变量 i的值为:"+i); 18 19 } 20 21 return i; 22 23 } 24 25 public static void main(String[] args){ 26 27 //创建Callable 对象 28 29 ThirdThread rt=new ThirdThread(); 30 31 //使用FutureTask来包装Callable对象 32 33 FutureTask<Integer> task= new FutureTask<Integer>(rt); 34 35 for(int i=0 ;i<20;i++){ 36 37 System.out.println(Thread.currentThread().getName()+"的循环变量为:"+i); 38 39 if(i==5){ 40 41 //开始线程 42 43 new Thread(task,"有返回值的线程").start(); 44 45 } 46 47 }try 48 49 { 50 51 System.out.println("子线程的返回值为:"+task.get()); 52 53 } 54 55 catch (Exception e) 56 57 { 58 59 e.printStackTrace(); 60 61 } 62 63 } 64 65 66 67 }
记住需要import java.util.concurrent.Callable; import java.util.concurrent.FutureTask;
程序最后调用FutureTask 对象的get方法来返回call方法的返回值------该方法将导致主线程被阻塞,直到call方法结束并返回为止。
如果代码为下面这样:
1 import java.util.concurrent.Callable; 2 3 import java.util.concurrent.FutureTask; 4 5 public class ThirdThread implements Callable<Integer> 6 7 { 8 9 //实现call()方法,作为线程的执行体 10 11 public Integer call(){ 12 13 int i=0; 14 15 for( ;i<20;i++){ 16 17 System.out.println(Thread.currentThread().getName()+"的循环变量 i的值为:"+i); 18 19 } 20 21 return i; 22 23 } 24 25 public static void main(String[] args){ 26 27 //创建Callable 对象 28 29 ThirdThread rt=new ThirdThread(); 30 31 //使用FutureTask来包装Callable对象 32 33 FutureTask<Integer> task= new FutureTask<Integer>(rt); 34 35 for(int i=0 ;i<20;i++){ 36 37 System.out.println(Thread.currentThread().getName()+"的循环变量为:"+i); 38 39 if(i==5){ 40 41 //开始线程 42 43 new Thread(task,"有返回值的线程").start(); 44 45 try{ 46 47 System.out.println("子线程的返回值为:"+task.get()); 48 49 } 50 51 catch (Exception e) 52 53 { 54 55 e.printStackTrace(); 56 57 } 58 59 } 60 61 62 63 } 64 65 } 66 67 68 69 }
那么输出结果为:
main的循环变量为:0
main的循环变量为:1
main的循环变量为:2
main的循环变量为:3
main的循环变量为:4
main的循环变量为:5
有返回值的线程的循环变量 i的值为:0
有返回值的线程的循环变量 i的值为:1
有返回值的线程的循环变量 i的值为:2
有返回值的线程的循环变量 i的值为:3
有返回值的线程的循环变量 i的值为:4
有返回值的线程的循环变量 i的值为:5
有返回值的线程的循环变量 i的值为:6
有返回值的线程的循环变量 i的值为:7
有返回值的线程的循环变量 i的值为:8
有返回值的线程的循环变量 i的值为:9
有返回值的线程的循环变量 i的值为:10
有返回值的线程的循环变量 i的值为:11
有返回值的线程的循环变量 i的值为:12
有返回值的线程的循环变量 i的值为:13
有返回值的线程的循环变量 i的值为:14
有返回值的线程的循环变量 i的值为:15
有返回值的线程的循环变量 i的值为:16
有返回值的线程的循环变量 i的值为:17
有返回值的线程的循环变量 i的值为:18
有返回值的线程的循环变量 i的值为:19
子线程的返回值为:20
main的循环变量为:6
main的循环变量为:7
main的循环变量为:8
main的循环变量为:9
main的循环变量为:10
main的循环变量为:11
main的循环变量为:12
main的循环变量为:13
main的循环变量为:14
main的循环变量为:15
main的循环变量为:16
main的循环变量为:17
main的循环变量为:18
main的循环变量为:19
也就验证了上面的情况 get方法会使主线程阻断。直到call方法结束后。才调用主线程。
创建线程的三种方式对比:
采用实现Runnable Callable 接口的方式创建多线程——
线程类只是实现了Runnable 接口或Callable 接口 ,还可以继承其他类。
在这种方式下,多线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU 、代码、和数据分开。
劣势是:编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThred()方法。
采用继承Thread类的方式创建多线程————
优势:编写简单,如果需要访问当前线程,则无须使用Thread.currentThread()方法。直接使用this 即可获得当前线程。
劣势:因为线程只能继承一个父类。所以它继承了Thread 不能在继承其他的父类了。