1、进程与线程的概念
现在几乎所有操作系统都支持多任务,通常一个任务就是一个程序,一个运行中的程序就是一个进程。当一个程序行时,其内部也可能在执行多个任务,进程内每一个任务的执行流,就是一个线程。
所以线程也被称作轻量级进程。
总而言之,线程是进程的组成部分,可以独立、并发的执行任务。
2、线程的创建和启动
Java中有两种方式来创建和启动线程。
2.1继承Thread类创建和启动线程
通过继承Thread类创建并启动多线程的步骤如下:
1、创建Thread的子类,并重写run方法。run方法中就是线程要执行的任务,所以也把run方法称为线程执行体。
2、创建该子类的实例,即线程对象。
3、使用线程对象的start方法启动线程。
线程类代码如下:
1 //通过继承Thread类来创建线程类 2 3 public class ThreadOne extends Thread{ 4 5 private int i; 6 7 8 9 //重写run方法,方法体就是线程要执行的任务 10 11 @Override 12 13 public void run() { 14 15 for (i = 0; i < 20; i++) { 16 17 //继承Thread类时,可以直接调用getName()方法来返回当前线程的名字 18 19 //如果想获取当前线程,直接使用this即可 20 21 System.out.println(getName()+" "+i); 22 23 } 24 25 } 26 27 }
测试代码如下:
1 public class TestThreadOne { 2 3 public static void main(String[] args) { 4 5 for (int i = 0; i < 10; i++) { 6 7 //打印主线线程的信息 8 9 System.out.println(Thread.currentThread().getName()+" "+i); 10 11 if (i==3) { 12 13 //创建并启动第一条线程 14 15 new ThreadOne().start(); 16 17 //创建并启动第二条线程 18 19 new ThreadOne().start(); 20 21 } 22 23 } 24 25 } 26 27 }
部分结果如下:
main 0 main 1 main 2 main 3 Thread-0 0 Thread-1 0 main 4 Thread-1 1 Thread-0 1 Thread-1 2 main 5
从上面的运行结果可以发现,测试类只显式的创建了两条线程,但实际上有三条线程在运行,即主线程、线程0和线程1。其中主线程的线程体是main方法里的内容。
此外,Thread-0和Thread-1两条线程输出的变量i的值是不连续的,而i并非是局部变量,而是实例成员变量,从这一点可以看出,每次创建线程都创建了一个ThreadOne对象。
2.2实现Runnable接口创建线程
实现Runnable接口创建并启动线程的步骤如下:
1、创建Runnable接口的实现类,重写run方法。run方法的内容即是线程要执行的任务。
2、创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象。该Thread对象才是真正的线程类。
线程类代码如下:
1 //通过实现Runnable接口创建线程类 2 3 public class ThreadTwo implements Runnable{ 4 5 private int i; 6 7 8 9 //run方法同样是线程的执行体 10 11 @Override 12 13 public void run() { 14 15 for (i = 0; i < 5; i++) { 16 17 //实现Runnable接口创建线程类时,只能使用Thread.currentThread()来获取当前线程 18 19 System.out.println(Thread.currentThread().getName()+" "+i); 20 21 } 22 23 } 24 25 }
测试代码如下:
1 public class TestThreadTwo { 2 3 public static void main(String[] args) { 4 5 for (int i = 0; i < 5; i++) { 6 7 //打印主线线程的信息 8 9 System.out.println(Thread.currentThread().getName()+" "+i); 10 11 if (i==3) { 12 13 ThreadTwo threadTwo = new ThreadTwo(); 14 15 //创建并启动第一条线程 16 17 new Thread(threadTwo).start(); 18 19 //创建并启动第二条线程 20 21 new Thread(threadTwo).start(); 22 23 } 24 25 } 26 27 } 28 29 }
运行结果如下:
1 main 0 2 3 main 1 4 5 main 2 6 7 main 3 8 9 Thread-0 0 10 11 Thread-1 0 12 13 main 4 14 15 Thread-1 2 16 17 Thread-0 1 18 19 Thread-0 4 20 21 Thread-1 3
从运行结果可以看出,Thread-0和Thread-1打印的i值是连续的,说明这两条线程是共享一个实例的。这是因为,我们所创建的两个Thread类使用同一个Runnable对象作为target。
在实际开发中,推荐使用实现Runnable接口的方式来创建线程,这样还可以实现或继
2.3有返回值的线程
从JDK1.5开始,java提供Callable接口和Future接口以获得线程的返回值。
Callable类似Runnable的加强版,提供一个call()方法作为线程的执行体。与Runnable的run()方法相比,call()方法更强大:
- call()方法可以有返回值
- call()可以声明抛出异常
不过Callable对象并不能直接作为Thread的target,必须使用Future对象包装一下。具体使用步骤如下:
- 创建Callable的实现类,并实现call方法,注意有泛型限制。
- 使用FutureTask包装Callable对象。
- 把FutureTask作为target传入Thread,创建启动线程。
- 调用FutureTask对象的方法获取返回值。
线程代码:
1 public class CallableOne implements Callable<Integer>{ 2 3 @Override 4 5 public Integer call() throws Exception { 6 7 int i = 0; 8 9 for(i=0;i<10;i++){ 10 11 System.out.println(Thread.currentThread().getName()+" "+i); 12 13 } 14 15 //线程返回值,模拟返回前阻塞主线程 16 17 Thread.sleep(5000); 18 19 return i; 20 21 } 22 23 }
测试代码:
1 public class TestCallable { 2 3 public static void main(String[] args) { 4 5 //创建Callable对象 6 7 CallableOne callableOne = new CallableOne(); 8 9 //使用FutureTask包装Callable对象 10 11 FutureTask<Integer> futureTask = new FutureTask<Integer>(callableOne); 12 13 for(int i=0;i<100;i++){ 14 15 System.out.println(Thread.currentThread().getName()+" "+i); 16 17 //i等于3时启动子线程,此时可见主线程与子线程交替执行 18 19 if (i==3) { 20 21 //FutureTask是Runnable的子类,可传入Thread当作target 22 23 new Thread(futureTask).start(); 24 25 } 26 27 //i等于80的时候来获取子线程的返回值,此时主线程会阻塞,直到返回了结果 28 29 if (i==80) { 30 31 try { 32 33 System.out.println("线程的返回值:"+futureTask.get()); 34 35 } catch (Exception e) { 36 37 e.printStackTrace(); 38 39 } 40 41 } 42 43 } 44 45 } 46 47 }
以上代码中,在i等于3时启动子线程,此时可见主线程与子线程交替执行。在i等于80的时候来获取子线程的返回值,此时主线程会阻塞,直到返回结果。