1. 进程与线程
1.1 进程
在提到线程之前,就得谈一谈进程。进程的概念在课本中的描述是:程序的一个执行实例,正在执行的程序;内核观点是:担当分配系统资源(CPU、内存)的实体。看到这里是不是觉得,那进程到底是什么,还是不懂,下面我将举例来说明进程究竟是什么?进程就像是学生,当学生第一次报道时,需要在学校填写你的详细信息,如身份证号、姓名、年龄,报道之后,学校会根据一定的顺序,给你分配一个在学校属于你的学号,这相当于你在学校的标识符;那么这么多学生,学校要怎么管理呢?这就是学校的教务管理系统要做的事情,它会把每个人的信息统计起来,然后存放在教务管理系统那儿,这样,每一个学生的信息就会被集中保存或者说处理或者说联系在了一起,方便学校去修改或者删除或者添加每个学生的信息。那么,学生是用学生的各种信息(如学号、身份证号、姓名等)描述的,进程用什么描述的呢?描述进程的是PCB,它保存着进程的相关信息(如标识符、状态、优先级等等)。学生的学生信息是用教务管理系统组织起来的,那么进程是用什么组织起来的呢?其实,所有运行在系统里的进程都是用带头结点的带环的双向链表组织起来的。
综上解释,重要的就是两句话:1.当执行一个程序即产生一个进程时,需要PCB把它描述起来;2.当执行一个程序即产生一个进程时,需要把这个进程的信息用链表的形式组织起来。
下面,来看一看在Windows操作系统下的进程是什么样子?打开Windows操作系统的任务管理器---点击进程即可:
以上是关于进程概念的简单介绍,想要深入了解进程,可以查看我的关于进程的博客。
1.2 线程
下面介绍什么是线程?
一个运行的程序即进程可以同时执行有多个任务,如QQ.exe可以接收文件、下载文件、发消息、发表情包等等,这些都是一个进程同时执行的多个任务;那线程就是一个一个的任务,如接收文件是一个线程、发表情包又是一个线程,所以线程可以理解为进程中独立运行的子任务。
2. 创建线程
了解了什么是线程之后,怎么来创建线程呢?
2.1 继承Thread类创建线程
首先,简单来看一看Thread类的源码以及在创建线程时用到的初始的run()方法:
使用继承Thread类创建线程时,首先需要创建一个类继承Thread类并覆写Thread类的run()方法,在run()方法中,要写线程要执行的任务。举例具体说明,代码如下所示:
class MyThread extends Thread{ @Override public void run() { System.out.println("自己创建的线程"); } }
创建了继承于Thread类的子类MyThread类以及覆写了Thread类的run()方法后,就相当于有了线程的主体类,接下来需要产生线程类的实例化对象然后调用run()方法,但实际上只是调用了run()方法并不是启动一个线程,真正启动一个线程,需要调用的是Thread类的start()方法,而start()方法会自动调用run()方法,从而启动一个线程。代码如下所示:
class MyThread extends Thread{ @Override public void run() { System.out.println("自己创建的线程"); } } public class Genericity { public static void main(String[] args) { //实例化一个对象 MyThread myThread=new MyThread(); //调用Thread类的start()方法 myThread.start(); //在main方法中打印一条语句 System.out.println("main方法"); } }
运行结果为: main方法 自己创建的线程
首先说明一点:main方法其实也是一个线程,是该进程的主线程。
那为什么会这样呢?明明应该先执行创建的线程,后打印语句"main方法。其实,在使用多线程技术时,代码的运行结果与代码调用的顺序无关,因为线程是一个子任务,CPU以不确定的方式或者说以随机的时间来调用线程中的run()方法,所以会出现我们所运行的结果!
注:一个对象多次调用start()方法时,会出现Expection in thread "main" java.lang.IllegalThreadStateExpection异常,错误代码示范如下:(错误代码可不要模仿的呦~)
class MyThread extends Thread{ @Override public void run() { System.out.println("自己创建的线程"); } } public class Genericity { public static void main(String[] args) { MyThread myThread=new MyThread(); //调用start()方法一次 myThread.start(); //调用start()方法两次 myThread.start(); System.out.println("main方法"); } }
此时就会出现异常:
Exception in thread "main" 自己创建的线程 java.lang.IllegalThreadStateException at java.lang.Thread.start(Unknown Source) at Genericity.main(Genericity.java:13)
2.2 实现Runnable接口创建线程
Thread类的核心功能就是进行线程的启动,但一个类为了实现多线程直接取继承Thread类时出现的问题就是:单继承的局限性!所以Java中还提供了另一种实现多线程的方法:实现Runnable接口来创建多线程。
先来看一看Runnable接口的源码:
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
再次提醒:一定一定一定要记得,启动一个线程的唯一方法就是调用Thread类的start()方法,抓住这一点去建立与Thread类之间的关系。
看到上述代码,你会发现Runnable接口只有一个抽象方法就是run()方法。那么怎么使用Runnable接口去创建线程呢?第一步:定义一个类来实现Runnable接口的抽象方法run()方法;那么问题来了?这个类怎么去调用Thread类的start()方法,从而启动一个线程呢?此时,Thread类有一个Thread类的构造方法public Thread(Runnable target)方法,参数用于接收Runnable接口的实例化对象,所以在Runnable接口与Thread类间就建立起了关系,从而可以调用Thread类的start()方法启动一个线程。所以第二步:利用Thread类的public Thread(Runnable target)构造方法与Runnable接口建立关系实例化Thread类的对象;第三步:调用Thread类的start()方法启动线程。具体代码如下:
//定义一个类MyThread实现Runnable接口,从而覆写run()方法 class MyThread implements Runnable{ @Override public void run() { System.out.println("利用Runnable接口创建线程"); } } public class Genericity { public static void main(String[] args) { //实例化Runnable接口的对象,其实也可以实例化MyThread类的对象,因为可以向上转型 Runnable runnable=new MyThread();//也可以改为 MyThread runnable=new MyThread(); //实例化Thread类的对象 Thread thread=new Thread(runnable); //调用Thread类的start()方法 thread.start(); //main线程中打印的一条语句 System.out.println("main方法"); } }
运行结果: main方法 利用Runnable接口创建线程
2.3 实现Callable接口创建线程
先来看看Callable接口的源码
@FunctionalInterface public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
Runnable接口的run()方法没有返回值,而Callable接口中的call()方法有返回值,若某些线程执行完成后需要一些返回值的时候,就需要用Callable接口创建线程。
提醒一万次+,非常重要的一点:启动一个线程的唯一方法就是调用Thread类的start()方法,那么我们要想通过实现Callable接口创建线程,就需要找到Callable接口与Thread类之间的关系。下面是它们的关系图:
从上图可以知道Callable接口与Thread类建立起了关系,所以使用Callable接口创建线程时,根据上图需要以下步骤:
(1)自定义一个类MyThread,实现Callable接口并覆写Callable接口的call()方法
(2)利用MyThread类实例化Callable接口的对象
(3)利用FutureTask类的构造方法public FutureTask(Claaable<V> callable),将Callable接口的对象传给FutureTask类
(4)将FutureTask类的对象隐式地向上转型,从而作为Thread类的public Thread(Runnable runnable)构造方法的参数
(5)这样就建立了Callable接口与Thread类之间的关系,再调用Thread类的start()方法
代码实现如下所示:
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; //1.定义一个类MyThread实现Callable接口,从而覆写call()方法 class MyThread implements Callable<String>{ @Override public String call() throws Exception { return "Callable接口创建线程"; } } public class Genericity { public static void main(String[] args) throws InterruptedException, ExecutionException { //2.利用MyThread类实例化Callable接口的对象 Callable callable=new MyThread(); //3.利用FutureTask类的构造方法public FutureTask(Claaable<V> callable) //将Callable接口的对象传给FutureTask类 FutureTask task=new FutureTask(callable); //4.将FutureTask类的对象隐式地向上转型 //从而作为Thread类的public Thread(Runnable runnable)构造方法的参数 Thread thread=new Thread(task); //5.调用Thread类的start()方法 thread.start(); //FutureTask的get()方法用于获取FutureTask的call()方法的返回值,为了取得线程的执行结果 System.out.println(task.get()); } }
运行结果: Callable接口创建线程
以上就是创建线程的三种方式!!!望纠正!愿改正!