- 如何了解多线程
- 线程与进程的区别
- 进程的基本原理
- 线程的基本原理
- 创建线程的4种方法
- 方法一:继承Thread类
- 方法二:实现Runnable 接口
- 方法三:实现Callable 接口
- 方法四:通过线程池来创建
- PS:写的不是很多,下面我会一部分一部分的给大家讲清楚的。谢谢!!!
你好! 今天要分享的是多线程,个人觉得分享最基础的感觉没什么意思,比如有几种基本类型,什么是封装、多态、继承等,希望各位看客自行学习。
线程与进程的区别根本区别: 其实进程是 *** 作系统分配资源的最小单位,线程是CPU处理器任务调度和执行的基本单位。
资源开销: 每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系: 如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线 (线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻 量级进程。
内存分配: 同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空 间和资源是相互独立的
影响关系: 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个 线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
执行过程: 每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是 线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控 制,两者均可并发执行。
PS
其实很多时候跟别的聊他们的区别的时候,上面的回答是对的,而且很多人会觉得你很NB。可是您真的懂这些吗?????
在计算机中,CPU是核心的硬件资源,承担了所有的计算任务;内存资源承担了运行时数据的保存任务;外存资源(硬盘等)承担了数据外部永久存储的任务。其中,计算任务的调度、资源的分配由 *** 作系统来统领。应用程序以进程的形式运行于 *** 作系统之上,享受 *** 作系统提供的服务。
进程的定义一直以来没有完美的标准。一般来说,一个进程由程序段、数据段和进程控制块三部分组成。
PS: 这3个部分的具体说明内容比较多,建议自行百度。。哈哈,偷懒下
说明那么多,对于JAVA程序员来说,什么是Java程序的进程呢?
Java编写的程序都运行在Java虚拟机(JVM)中,每当使用Java命令启动一个Java应用程序时,就会启动一个JVM进程。在这个JVM进程内部,所有Java程序代码都是以线程来运行的。JVM找到程序的入口点main()方法,然后运行main()方法,这样就产生了一个线程,这个线程被称为主线程。当main()方法结束后,主线程运行完成,JVM进程也随即退出。(大概可以看到进程跟线程是有包含的意义)
**由来:**早期的 *** 作系统只有进程而没有线程。进程是程序执行和系统进行并发调度的最小单位。随着计算机的发展,CPU的性能越来越高,从早期的20MHz发展到了现在的2GHz以上,从单核CPU发展到了多核CPU,性能提升了成千上万倍。为了充分发挥CPU的计算性能,提升CPU硬件资源的利用率,同时弥补进程调度过于笨重产生的问题,进程内部演进出了并发调度的诉求,于是就发明了线程。
线程是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是 *** 作系统资源分配的最小单位。
Java程序的进程执行过程就是标准的多线程的执行过程。每当使用Java命令执行一个class类时,实际上就是启动了一个JVM进程。理论上,在该进程的内部至少会启动两个线程,一个是main线程,另一个是GC(垃圾回收)线程。实际上,执行一个Java程序后,通过Process Explorer来观察,线程数量远远不止两个,达到了18个之多。一个标准的线程主要由三部分组成,即线程描述信息、程序计数器(ProgramCounter,PC)和栈内存(这个具体的等讲JVM的时候在聊)
说下线程信息:主要包含
- 线程ID(Thread ID,线程标识符)。线程的唯一标识,同一个进程内不同线程的ID不会重叠。
- 线程名称。主要是方便用户识别,用户可以指定线程的名字,如果没有指定,系统就会自动分配一个名称。
- 线程优先级。表示线程调度的优先级,优先级越高,获得CPU的执行机会就越大。
- 线程状态。表示当前线程的执行状态,为新建、就绪、运行、阻塞、结束等状态中的一种。
- 其他。
需要继承Thread类,创建一个新的线程类。同时重写run()方法,将需要并发执行的业务代码编写在run()方法中
- start()方法:用来启动一个线程,当调用start()方法后,JVM才会开启一个新的线程来执行用户定义的线程代码逻辑,在这个过程中会为相应的线程分配需要的资源。
- run() 方法:作为线程代码逻辑的入口方法。run()方法不是由用户程序来调用的,当调用start()方法启动一个线程之后,只要线程获得了CPU执行时间,便进入run()方法体去执行具体的用户线程代码。
总之:start()方法用于线程的启动,run()方法作为用户代码逻辑的执行入口。(很多面试的时候会问到他们的区别)
方法二:实现Runnable 接口创建线程的第二种方法就是实现Runnable接口,将需要异步执行的业务逻辑代码放在Runnable实现类的run()方法中,将Runnable实例作为target执行目标传入Thread实例。该方法的具体步骤如下:
- 定义一个新类实现Runnable接口。
- 实现Runnable接口中的run()抽象方法,将线程代码逻辑存放在该run()实现版本中。
- 通过Thread类创建线程对象,将Runnable实例作为实际参数传递给Thread类的构造器,由Thread构造器将该Runnable实例赋值给自己的target执行目标属性。
- 调用Thread实例的start()方法启动线程。
- 线程启动之后,线程的run()方法将被JVM执行,该run()方法将调用target属性的run()方法,从而完成Runnable实现类中业务代码逻辑的并发执行。
缺点:
- 所创建的类并不是线程类,而是线程的target执行目标类,需要将其实例作为参数传入线程类的构造器,才能创建真正的线程。
- 如果访问当前线程的属性(甚至控制当前线程),不能直接访问Thread的实例方法,必须通过Thread.currentThread()获取当前线程实例,才能访问和控制当前线程。
优点:
- 可以避免由于Java单继承带来的局限性。如果异步逻辑所在类已经继承了一个基类,就没有办法再继承Thread类。比如,当一个Dog类继承了Pet类,再要继承Thread类就不行了。所以在已经存在继承关系的情况下,只能使用实现Runnable接口的方式。
- 逻辑和数据更好分离。通过实现Runnable接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。在同一个资源被多个线程逻辑异步、并行处理的场景中,通过实现Runnable接口的方式设计多个target执行目标类可以更加方便、清晰地将执行逻辑和数据存储分离,更好地体现了面向对象的设计思想。
前面已经介绍了继承Thread类或者实现Runnable接口这两种方式来创建线程类,但是这两种方式有一个共同的缺陷:不能获取异步执行的结果。这是一个比较大的问题,很多场景都需要获取异步执行的结果,通过Runnable无法实现,是因为它的run()方法不支持返回值。
Callable接口是一个泛型接口,也是一个“函数式接口”。其唯一的抽象方法call()有返回值,返回值的类型为Callable接口的泛型形参类型。call()抽象方法还有一个Exception的异常声明,容许方法的实现版本的内部异常直接抛出,并且可以不予捕获。Callable接口类似于Runnable。不同的是,Runnable的唯一抽象方法run()没有返回值,也没有受检异常的异常声明。比较而言,Callable接口的call()有返回值,并且声明了受检异常,其功能更强大一些。
问题: Callable实例能否和Runnable实例一样,作为Thread线程实例的target来使用呢?答案是不行。Thread的target属性的类型为Runnable,而Callable接口与Runnable接口之间没有任何继承关系,并且二者唯一的方法在名字上也不同。显而易见,Callable接口实例没有办法作为Thread线程实例的target来使用。既然如此,那么该如何使用Callable接口创建线程呢?一个在Callable接口与Thread线程之间起到搭桥作用的重要接口马上就要登场了--------RunnableFuture接口
使用Callable和FutureTask创建线程的具体步骤
- 创建一个Callable接口的实现类,并实现其call()方法,编写好异步执行的具体逻辑,可以有返回值。
- 使用Callable实现类的实例构造一个FutureTask实例。
- 使用FutureTask实例作为Thread构造器的target入参,构造新的Thread线程实例。
- 调用Thread实例的start()方法启动新线程,启动新线程的run()方法并发执行。其内部的执行过程为:启动Thread实例的run()方法并发执行后,会执行FutureTask实例的run()方法,最终会并发执行Callable实现类的call()方法。
- 调用FutureTask对象的get()方法阻塞性地获得并发线程的执行结果。
public class MyCallable implements Callable{ @Override public Integer call() { System.out.println(Thread.currentThread().getName() + " call()方法执行中..."); return 1; } } public class CallableTest { public static void main(String[] args) { FutureTask futureTask = new FutureTask (new MyCallable()); Thread thread = new Thread(futureTask); thread.start(); try { Thread.sleep(1000); System.out.println("返回结果 " + futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " main()方法执行完成"); } } 执行结果 1 Thread‐0 call()方法执行中... 2 返回结果 1 3 main main()方法执行完成
PS: 这里讲的不是很清楚,下次我会单独拉出来,给大家讲解一下Callable 接口如何创建线程,多多包涵
方法四:通过线程池来创建所创建的Thread实例在执行完成之后都销毁了,这些线程实例都是不可复用的。实际上创建一个线程实例在时间成本、资源耗费上都很高(稍后会介绍),在高并发的场景中,断然不能频繁进行线程实例的创建与销毁,而是需要对已经创建好的线程实例进行复用,这就涉及线程池的技术。Java中提供了一个静态工厂来创建不同的线程池,该静态工厂为Executors工厂类。
Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。主要有newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool,具体的以后在说。
1 public class MyRunnable implements Runnable { 2 3 @Override 4 public void run() { 5 System.out.println(Thread.currentThread().getName() + " run()方法执行中..."); 6 } 7 8 } 1 public class SingleThreadExecutorTest { 2 3 public static void main(String[] args) { 4 ExecutorService executorService = Executors.newSingleThreadExecutor(); 5 MyRunnable runnableTest = new MyRunnable(); 6 for (int i = 0; i < 5; i++) { 7 executorService.execute(runnableTest); 8 } 9 10 System.out.println("线程任务开始执行"); 11 executorService.shutdown(); 12 } 13 14 } 执行结果 1 线程任务开始执行 2 pool‐1‐thread‐1 is running... 3 pool‐1‐thread‐1 is running... 4 pool‐1‐thread‐1 is running... 5 pool‐1‐thread‐1 is running... 6 pool‐1‐thread‐1 is running...PS:写的不是很多,下面我会一部分一部分的给大家讲清楚的。谢谢!!!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)