文章目录
Java多线程
多线程概述
线程和进程都是一种抽象的概念,线程是一种比进程还小的抽象,线程和进程都可用于实现并发。 一个进程里只有一个线程,进程本身就是线程。 所以线程有时被称为轻量级进程 (也可以是一个)线程。
进程
一般由程序,数据集合和进程控制块三部分组成。
进程一般指的是正在执行的程序,其实就是应用程序在内存中运行的那片空间。
线程
进程中的执行单元。负责进程中的程序执行,一个进程中,至少有一个线程。
进程与线程的区别
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
调度和切换:线程上下文切换比进程上下文切换要快得多
多线程
Java 给多线程编程提供了内置的支持。一个多线程程序包含两个或多个能并发运行的部分。程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径。
多线程是多任务的一种特别的形式。多线程比多任务需要更小的开销。
多线程能满足程序员编写非常有效率的程序来达到充分利用 CPU 的目的,因为 CPU 的空闲时间能够保持在最低限度
创建线程的方法
Java 提供了三种创建线程的方法:
通过实现 Runnable 接口
创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。
为了实现 Runnable,一个类只需要执行一个方法调用 run()
通过继承 Thread 类本身
本质上也是实现了 Runnable 接口的一个实例
创建一个线程的一种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。
继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
调用run方法和start方法的区别调用run方法不开启线程,仅仅是对象调用方法
start方法,开启新线程并让线程执行,同时能调用run方法。可以避免在主函数中,只有一个主线程来负责两个线程对象
通过 Callable 和 Future 创建线程。
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
创建方法对比
1.采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
2.使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
3.Runnable 方法更优:
易于扩展:
这个不多说,Java 是单继承。如果使用继承 Thread 的写法。将不利于后续扩展。
解耦:
用 Runnable 负责定义 run () 方法(执行内容)。这种情况下,它与 Thread 实现了解耦。Thread 负责线程的启动以及相关属性设置。
性能:
在一些情况下可以提高性能。比如:线程执行的内容很简单,就是打印个日志。如果使用 Thread 实现,那它会从线程创建到销毁都要走一遍,需要多次执行时,还需要多次走这重复的流程,内存开销非常大。但是我们使用 Runnable 就不一样了。可以把它扔到线程池里面,用固定的线程执行。这样,显然是可以提高效率的。
线程池创建
线程池创建线程本质上是默认通过
DefaultThreadFactory
线程工厂来创建的。
四种方式:
newCachedThreadPool 创建一个可缓存的线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行
newSingleThreadExecutor 创建一个单线程化的线程池,它只会唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行
线程池的优点:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
线程同步和异步
多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理。显然这是由于全局资源造成的,有时为了解决此问题,优先考虑使用局部变量,退而求其次使用同步代码块,出于这样的安全考虑就必须牺牲系统处理性能,加在多线程并发时资源挣夺最激烈的地方,这就实现了线程的同步机制
线程同步
例:A线程要请求某个资源,但是此资源正在被B线程使用中,因为同步机制存在,A线程请求不到,怎么办,A线程只能等待下去
主要用于协调对临界资源的访问,临界资源可以是硬件设备(比如打印机)、磁盘(文件)、内存(变量、数组、队列等)。
线程同步有4种机制:
临界区
互斥量
事件
信号量
线程异步
例:A线程要请求某个资源,但是此资源正在被B线程使用中,因为没有同步机制存在,A线程仍然请求的到,A线程无需等待
显然,同步最最安全,最保险的。而异步不安全,容易导致死锁,这样一个线程死掉就会导致整个进程崩溃,但没有同步机制的存在,性能会有所提升
关于线程池
线程池可以用HashTable这种数据结构来实现,看了Apach HTTP服务器的线程池的源代码,用是就是HashTable,KEY用线程对象,value 用ControlRunnable,ControlRunnable是线程池中唯一能干活的线程,是它指派线程池中的线程对外提供服务。出于安全考虑,Apach HTTP服务器的线程池它是同步的。
为了防止多个线程并发对同一数据的修改,所以需要同步,否则会造成数据不一致(就是所谓的:线程安全。如java集合框架中Hashtable和Vector是线程安全的。
Java并发编程:volatile关键字
volatile是Java提供的一种轻量级的同步机制。Java语言包含两种同步机制:1、同步块(或同步方法) 2、volatile变量。相比于synchronized加锁同步,volatile关键字比较轻量级,开销更低,因为他不会引起线程上下文的切换调度。
简单概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道。
volatile关键字保证了共享变量在多个线程中的可见性。当一个线程修改了共享变量的值时,其他的线程能够看见这个值的改变,并且将重新读取该值。
拓展:协程
什么是协程
协程, 我们又称为微线程,协程它不像线程和进程那样,需要进行系统内核上的上下文切换,协程的上下文切换是由开发人员决定的。协程是一种用户级的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。