基础概念
进程:简单来说就是计算机内存中运行的应用程序,有自己独立的地址空间,并且不同进程的地址空间是相互隔离的,是资源分配的最小单位。
线程:是程序中的顺序控制流,表示程序的执行流程,本身需要依靠程序(进程)进行运行,只能使用分配给程序的资源和环境,是CPU调度的最小单元。
单线程:程序中只存在一个线程,实际上通常main主方法就是一个主线程。
多线程:通常为了更好地使用CPU资源,在一个程序中运行了多个任务(也就是指这个程序或进程在运行时产生了不止一个线程)
并行:多个CPU实例或者多台机器同时执行一段处理逻辑,是真正的同时执行。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
线程安全:通常指在并发情况下,如果一段代码在多线程下执行和在单线程下执行永远都能获得一致的结果,那么就表明这段代码是线程安全的。
同步:通常指通过人为地控制和干涉,保证多线程情况下对于共享资源的访问变得线程安全,从而保证程序最终执行结果的准确。
进程和线程的区别:
- 进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元。
- 同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进程至少包括一个线程。
- 进程拥有自己独立的地址空间,包含程序内容和数据,并且不同进程的地址空间都是相互隔离的。而同一进程中的线程共用相同的地址空间,同时共享进程所拥有的内存和其他资源。
- 进程之间的切换开销大,而线程切换开销比较小。
多线程的作用:
-
发挥多核CPU的优势
单核CPU上所谓的“多线程”那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程“同时”运行罢了。 -
防止阻塞
多条线程同时运行,哪怕其中某一条线程的代码执行读取数据阻塞,也不会影响其他任务的执行。 -
便于建模
假设有一个大的任务A,单线程编程的话就需要考虑得很多,建立整个程序模型相对来说比较麻烦,但是如果把这个大的任务A分解成几个小的任务B,任务C,任务D然后分别建立程序模型,并通过多线程分别运行这几个任务,相对来说就简单多了。
线程的状态
1. 创建(NEW)状态
线程在被创建后,进入NEW状态。
2. 就绪(RUNNABLE)状态
调用start()方法,进入就绪状态,等待系统调度,分配资源,此时的调度顺序是无法确定的。
3. 运行(RUNNING)状态
- 执行run()方法,获得CPU执行时间片,进行运行状态
- 调用yield()方法可以让一个running状态的线程转入runnable
4. WAITING状态
调用wait(),join()方法,进入主动等待状态,等待notify(),notifyAll()被唤醒
5. TIME WAITING
调用sleep(senconds),wait(seconds),join(seconds),主动睡眠
6. 阻塞(BLOCKED)状态
被同步块阻塞,或者遇到IO阻塞,进入阻塞状态,暂时停止执行,可以将资源交给其他线程使用
7. 终止(DEAD)状态
线程销毁
线程在Running过程中可能会遇到的阻塞(Blocked)情况
- 调用join()或者sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待CPU的再次调度
- 调用wait(),使该线程处于等待池(wait blocked pool),直到调用notify()/notify all(),线程被唤醒再被放到锁定池(lock blocked pool),释放同步锁使线程回到可运行状态
- 对Running状态的线程加同步锁(Synchronized),使其进入锁定池(lock blocked pool),同步锁被释放进入可运行状态
常用方法
静态方法
currentThread()方法
可以返回代码段正在被哪个线程调用的信息。
sleep()方法
- 让线程睡眠,交出CPU时间片,让CPU去执行其他的任务
- 但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象
yield()方法
- 调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程
- 它跟sleep方法类似,同样不会释放锁,但是yield不能控制具体的交出CPU的时间
- yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会
- 调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的
对象方法
start()方法
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源
run()方法
- run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务
- 继承Thread类必须重写run方法,在run方法中定义具体要执行的任务
getId()方法
getId()的作用是取得线程的唯一标识
isAlive()方法
- 方法isAlive()的功能是判断当前线程是否处于活动状态
- 什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的
join()方法
在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁
getName()和setName()方法
用来获取或者设置线程名称
getPriority()和setPriority()方法
用来获取和设置线程优先级
setDaemon()和isDaemon()方法
用来设置线程是否成为守护线程和判断线程是否是守护线程
创建线程的方式
- 继承Thread类
/**
* 自定义线程
*/
class MyThread extends Thread{
/*线程名称*/
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("名称" + name + "的线程ID是:" + Thread.currentThread().getId());
}
}
- 实现Runnable接口
class MyRunnable implements Runnable{
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("名字" + name + "的线程ID是: " + Thread.currentThread().getId());
}
}
- 通过线程池创建
//创建一个线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("==========================");
}
});
暂停线程
调用interrupt()方法
它并不是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的。
停止线程
在Java中有以下3种方法可以终止正在运行的线程
- 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
- 使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果
- 使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止
最后,再附上一张整理的思维导图:
JAVA多线程.png
网友评论