1.什么是进程和线程
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程,进程--资源分配的最小单位
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程--程序执行的最小单位
进程和线程对比:进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
进程拥有共享的资源,如内存空间等,供其内部的线程共享
进程间通信较为复杂
线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
2.线程的状态
线程有五种状态(基于操作系统):新建--就绪--运行--阻塞-死亡
新建:一旦创建了线程对象就处于新建状态
就绪:一旦调用了start()方法就处于就绪状态,至于何时处于运行状态,要取 决于cpu
运行:当前线程获得了cpu即处于运行状态
阻塞:当前线程调用了sleep方法,在等待通知,想要获得同步监视器,IO阻塞、suspend()方法调用了
死亡:Error或Exception/run()/call()执行完成
线程有六种状态(基于JavaAPI):新建--运行中(就绪,运行,阻塞)--死亡
新建: 一旦创建了线程对象就处于新建状态
运行中:运行中包括了操作系统种的三种状态,就绪--运行--阻塞
阻塞:javaAPI中的阻塞分为Blocked,Waiting,Time-Waiting
死亡:Error或Exception/run()/call()执行完成
3.线程创建的三种方式(在这里都用匿名内部类的方式实现)
- 继承Thread类
new Thread("线程一"){ @Override public void run() { System.out.println("你好!"); } }.start();
- 实现Runnable接口
new Thread(new Runnable() { @Override public void run() { while (true){ log.debug("线程一正在running"); // System.out.println("线程一正在running"); } } },"线程一").start();
- 实现Callable接口(这里需要使用FutureTask来接收call()方法的返回值)
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { return 1; } }); Thread thread = new Thread(futureTask, "线程一"); thread.start(); Integer integer = futureTask.get(); System.out.println(integer);
4.线程运行之原理
栈与栈帧: 我们都知道java虚拟机是由栈、堆、方法区组成,那么栈内存是给谁用的呢,是给线程用的,每个线程启动后,Java虚拟机就会为其分配一个栈内存,每个栈由多个栈帧组成,对应着每次方法调用时所占的内存,每个线程只能有一个活动栈帧,对应着正在执行的那个方法
线程上下文切换:当发生如下情况时会导致线程上下文切换(即CPU不执行当前线程,转而执行另一个线程)
线程的时间片用完
垃圾回收(停掉所有用户线程,转而由执行垃圾回收任务的那个线程执行)
有更高优先级的线程需要执行
线程自己调用了sleep,yield,join,wait,park,synchronized,lock等方法
需要注意的是频繁的上下文切换会影响性能,原因如下:当发生频繁的上下文切换时,需要操作系统保存当前线程的状态,恢复另一个线程的状态,java中对应的概念就是程序计数器(它的作用是记住下一条jvm指令的执行地址),它是线程私有的。状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
5.常用方法
start()
功能说明:启动一个新线 程,在新的线程运行run 方法中的代码
注意:start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出IllegalThreadStateException
run()
功能说明:新线程启动后会调用的方法
注意:如果在构造 Thread 对象时传递了 Runnable 参数,则 线程启动后会调用 Runnable 中的 run 方法,否则默 认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为
join()
功能说明:等待线程运行结 束
join(long n)
功能说明:等待线程运行结 束,最多等待 n 毫秒
getId()
功能说明:获取线程长整型 的 id
注意:id 唯一
getName()
功能说明:获取线程名
setName(String)
功能说明:获取线程名
getPriority()
功能说明:获取线程优先级
setPriority(int)
功能说明:修改线程优先级
注意:java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被CPU调度的机率
getState()
功能说明:获取线程状态
注意:Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupted()
功能说明:判断是否被打 断
注意:不会清除 打断标记
isAlive()
功能说明:线程是否存活 (还没有运行完 毕)
interrupt()
功能说明:打断线程
注意:如果被打断线程正在 sleep,wait,join 会导致被打断 的线程抛InterruptedException,并清除 打断标 记 ;如果打断的正在运行的线程,则会设置 打断标 记 ;park 的线程被打断,也会设置 打断标记
interrupted()
功能说明:判断当前线程是 否被打断
注意:会清除打断标记
currentThread()
功能说明:获取当前正在执行的线程
sleep(long n)
功能说明:让当前执行的线 程休眠n毫秒,休眠时让出cpu的时间片给其它线程
yield()
功能说明:提示线程调度器 让出当前线程对 CPU的使用
注意:主要是为了测试和调试
6.方法区别
run()和start()方法
调用直接调用run方法不会启动线程,是在主线程中执行了run方法,需要调用start方法才会启动线程并执行run方法中的代码
sleep()和yield()方法
sleep()方法
调用sleep()方法会让当前线程从RUNNING状态变为TIME WAITING状态(阻塞)
其他线程可以调用interrupte()方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
睡眠结束后的线程未必会立刻得到执行
使用TimeUnit代替Thread的sleep可以拥有更好的可读性
yield()方法
调用yield方法会让当前线程从RUNNING状态变为Runnable就绪状态,然后调度执行其他程序
具体的实现有赖于操作系统的任务调度器
7.线程优先级
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
8.同步和异步
需要等待结果返回,才能继续运行就是同步
不需要等待结果返回,就能继续运行就是异步
9.不推荐的方法
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
stop() 停止线程运行
suspend() 挂起(暂停)线程运行
resume() 恢复线程运行
3.wait和sleep
1.wait属于Thread类,而sleep属于Object,故每个对象都可以调用sleep方法
2.wait会释放锁,而sleep不会释放锁
3.都可以被interrupted方法中断
4.并发和并行
并发:指的是多个线程竞争同一个资源,多个线程对一个点
并行:多项工作一起执行,之后再汇总,多个线程对多个点
结论:单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用 cpu ,不至于一个线程总占用 cpu,别的线程没法干活
多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的
IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一 直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化
5.管程
管程在操作系统中被称为监视器,在Java中被称为锁
是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或代码
6.用户线程和守护线程
用户线程:自定义线程,自己通过继承Thread类,实现Runnable接口,实现Callable接口等都是用户线程
守护线程:即垃圾回收线程