一、线程概述
1.进程和线程的区别
进程:进程是处于运行过程中的程序,并且具有一定的功能,是系统进行资源分配和调度的一个独立单位。
主要特征:
(1)独立性:系统中独立存在的实体,拥有自己的独立资源,拥有自己的私有地址空间,一个用户进程在不经过进程本身允许的情况下,
不能访问其他进程的地址空间。
(2)动态性:体现在进程和程序的区别。程序的静态的指令集合,而进程则是动态的指令集合,添加了时间。
(3)并发性:多个进程在单个CUP上并发执行,多个进程之间不会相互影响。
补充:并发:在同一时刻执行一条指令,但是多个进程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果。
并行:在同一时刻,有多条指令在多个处理器上同时执行。
线程:进程的执行单位,在进程中独立的,并发的执行流,拥有自己的堆栈,自己的程序计数器和自己的局部变量,不拥有资源,与父进程的线程
共享该进程的全部资源。
主要特征:独立运行,抢占式执行。
总结:操作系统可以同时执行多个任务,每个任务就是进程,进行也可以同时执行多个任务,每个任务就是线程。
2.多线程的优势
(1)进城之间不能共享内存,但是线程之间内存共享非常容易。
(2)系统创建进程需要为该进程重新分配系统资源,但是创建线程代价小,因此开发效率较高。
(3)Java语言内置了多线程的功能支持,而不是单纯的作为底层操作系统的调度方式,从而简化Java的多线程编程。
二、线程的创建和启动
1.创建线程的两三种方法
(1)继承Thread类创建线程类
通过继承该类创建并启动线程的步骤如下:
a.定义Thread的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此run()方法称为线程执行体。
b.创建Thread子类的实例,即创建了线程对象。
c.调用线程对象的start()方法来启动线程。
实例如下:
/** * * @author fengkuirui * @date 2017-02-09 * 通过继承Thread类创建线程 */ public class FirstThread extends Thread { private int i; //重写run()方法 @Override public void run(){ for(;i<100;i++){ //当线程继承Thread类时,直接实用this即可获得当前线程 //Thread类的getName()返回当前线程的名字 System.out.println(this.getName()+" "+i); } } public static void main(String[] args) { for(int i=0; i<100; i++){ //调用Thread的currentThread()方法获取当前线程。 System.out.println(Thread.currentThread().getName()+" "+i); if(i == 20){ //创建并启动第一个线程 new FirstThread().start(); //创建启动第二个线程 new FirstThread().start(); } } } }
运行结果如下:
注意:通过继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
(2)实现Runnable接口创建线程类
(1)步骤
a.定义Runnable接口的实现类,并重写该接口的run()方法。
b.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
c.调用start()方法启动该线程。
(2)实例如下:
/** * * @author fengkuirui * @date 2017-02-09 * 通过实现Runnable接口创建线程类 */ public class SecondThread implements Runnable{ private int i; @Override public void run(){ for(;i<100;i++){ //输出当前线程的名字; System.out.println(Thread.currentThread().getName()+" "+i); } } public static void main(String[] args) { for(int i=0; i<100; i++){ System.out.println(Thread.currentThread().getName()+" "+i); if(i == 20){ SecondThread st = new SecondThread(); //通过new Thread(target,name)方法创建新线程; new Thread(st,"新线程1").start(); new Thread(st,"新线程2").start(); } } } }
(3)运行结果如下:
注意:采用Runnable接口的方式创建的多个线程是可以共享线程类的实例变量。
3.使用Callable和热Future创建线程
(1)步骤
a.创建Callable接口的实现类,并实现call()方法,该方法将作为线程执行体,并且该方法有返回值,再创建Callable实现类的实例。
b.使用FutureTask类来包装Callable实现类对象,该FutureTask对象封装了该Callable实现类对象的call()方法的返回值。
c.使用FutureTask对象作为Thread对象的target创建并启动线程。
d.调用FutureTask对象的get()方法来实现线程类,并启动该线程。
(2)实例如下:
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; /** * * @author fengkuirui * @date 2017-02-09 * 使用Callable和Future创建线程 */ public class ThridThread implements Callable<Integer>{ @Override public Integer call() throws Exception { // TODO Auto-generated method stub int i=0; for(;i<100;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } //call()可以有返回值; return i; } public static void main(String[] args) { //创建Callable对象 ThridThread rt = new ThridThread(); //使用FutureTask来包装Callable对象 FutureTask<Integer> task = new FutureTask<>(rt); for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+" "+i); if(i == 20){ //实质还是以Callable对象创建并启动线程; new Thread(task,"有返回值的线程").start(); } } try{ //获取线程返回值 System.out.println("子线程返回值: "+task.get()); }catch(Exception e){ e.printStackTrace(); } } }
运行结果如下:
2.三种创建线程方式的区别
(1)采用实现Runnable接口、Callable接口的方式创建线程的优缺点:
a.线程类还可以继续继承其他类
b.这种方式多线程共享一个target对象,所以非常适合多个相同的线程来处理同一份资源情况下,采用此种方式。
缺点:编程稍微复杂,需要访问当前线程时,必须使用Thread.currentThread()方法。
(2)采用继承Thread类来创建线程的优缺点:
优点:编写简单。
缺点:不能继承其他父类。