多线程
一、概念
1、进程和线程的概念
- 进程和线程都是操作系统中的概念,操作系统对进程和线程的严格定义是:进程是具有一定独立功能的程序,是系统进行资源分配和调度的独立单位。线程是进程的实体,是cpu调度和分配的基本单位。
2、进程与线程的关系
- 一个线程只能属于一个进程,而一个进程可以有多个线程。
- 同一进程的所有线程共享该进程的资源。
- 线程在执行过程中,不同进程的线程间要利用消息通信的办法实现数据同步。
3、线程的生命周期
- 主要有创建,就绪,运行,阻塞,销毁几个状态,如图:
2、java与多线程
- java为了提升程序性能,也引入了多线程概念,通过多线程,在一些场景能极大提升性能,实现线程主要有两种方式,一种继承Thread类,另一种实现Runable接口,
2.1 继承Thread类型
- 创建线程,可以通过继承Thread类并实现run()方法的形式,run()里面编写实际要运行的代码,运行时通过start()方法将线程设为可运行态,并不是直接执行,具体调用还是要操作系统决定。
1 | /** |
2.2 实现Runnable接口
- 线程类通过实现Runnable接口,实现run()方法,也能实现线程调用,和继承Thread类类似,如下:
1 | public class RunnableThread implements Runnable{ |
2.3 详细的启动过程
- 1、通过new创建一个线程对象
- 2、调用线程对象的start()方法,使线程处理Runnable可运行状态,等待调用
- 3、当被调用时,执行run()中的代码
- 4、当线程因为原因在运行时放弃cpu调用时(比如被优先级高的线程抢占),便会进入阻塞状态,当要继续运行时,需要重新进入可运行状态。
- 5、执行完成,会进入销毁状态。
3、线程同步
- 同一个进程的线程资源是共享的,当操作共享资源时,会存在问题,为了避免临界区造成的问题,线程之间需要进行同步,主要有下面几种同步方式
3.1、使用synchronized进行修饰
- 在需要同步的方法面前加入synchronized进行修饰,synchronized会将对象锁住,能够保证在同一时刻只有一个线程访问,在调用方法之前,需要先获得内置锁,同时,除了修饰方法,还能修饰代码块
1 | public void synchronized saveMoney(){//todo somethind}; |
1 | synchronized(object){}; //修饰对象 |
3.2、使用Lock锁
- 锁机制也能保证同一个时刻只能有一个线程进行访问,在在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock可重入锁等
1 | Lock l = ...; //lock实现对象对象 |
3.3 volatile
- volatile主要保证了一个共享变量在多个线程访问的情况下,保证了共享变量的可见性,比如一个线程对变量进行了修改,另一个线程能同步到修改的值。
4、线程池
- 在单个线程的使用中,频繁的创建销毁线程对象损耗较多的时间,影响系统性能,当有任务来时,临时创建性能也非常低效,节省了时间和效率。所以在多线程的开发中,尽量使用线程池的形式。
4.1、ThreadPoolExecutor
- 使用ThreadPoolExecutor进行创建线程池,用法
1 | public ThreadPoolExecutor(int corePoolSize, |
- corePoolSize是核心池的大小,maximumPoolSize最大线程数,keepAliveTime当没有任务时线程的存活时间,weekQueue阻塞队列,用来保存执行的任务,unit时间单位
- 协作流程:线程优先向 CorePool 中提交;在 Corepool 满了之后,线程被提交到任务队列,等待线程池空闲;在任务队列满了之后 corePool 还没有空闲,那么任务将被提交到 maxPool 中,如果 MaxPool 满了之后执行 task 拒绝策略。