一、进程与线程
认识
- 程序由指令和数据组成,简单来说,进程可以视为程序的一个实例
- 大部分程序可以同时运行多个实例进程,例如记事本、画图、浏览器等
- 少部分程序只能同时运行一个实例进程,例如QQ音乐、网易云音乐等
- 一个进程可以分为多个线程,线程为最小调度单位,进程则是作为资源分配的最小单位
- 在Windows中进程是不活动的,只是作为线程的容器
对比
- 进程之间基本相互独立,而线程存在于进程内,是进程的一个子集
- 进程拥有的资源供其内部的线程共享
- 进程间通信较为复杂
- 同一台计算机的进程通信称为IPC
- 不同计算机之间的进程通信需要通过网络,遵守共同的协议,例如HTTP
- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
二、概念总览
并行与并发
- 同一时刻同时发生,即为并行
- 同一时段同时发生,即为并发
同步与异步
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,自顾自地运行就是异步
三、创建和运行线程
- 方法一:直接使用Thread
@Test
public void test1(){
//创建一个新线程
Thread thread1=new Thread("t1"){
//设置线程的具体任务
public void run(){
log.info("success");
}
};
//启动线程
thread1.start();
log.info("error");
}
- 方法二:使用Runnable配合Thread
用这种方式将线程Thread和任务Runnable分开,更容易组件化操作
@Test
public void test2(){
Runnable runnable=new Runnable() {
@Override
public void run() {
log.info("success");
}
};
//创建新线程并命名
Thread thread2=new Thread(runnable,"t2");
thread2.start();
log.info("error");
}
- 方法三:使用FutureTask配合Thread
@Test
public void test4() throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask=new FutureTask<>(new Callable<Integer>() {
//使用Callable还有一个好处就是能够返回值,而Runnable不行
@Override
public Integer call() throws Exception {
log.info("success");
Thread.sleep(1000);
return 100;
}
});
Thread thread4=new Thread(futureTask,"t4");
thread4.start();
//捕获到线程执行完成并获取返回值进行后续处理
Integer result=futureTask.get();
log.info("error:{}",result);
}
四、查看进程线程的方法
Windows
- 可以通过任务管理器来看
- tasklist:查看进程
- taskkill:杀死进程
Linux
- ps -ef 查看所有进程
- ps -ef | grep java :查询带Java的进程
- kill 杀死进程,kill -9 强制杀死进程
- top 进程排名
Java
- jps:查看所有Java进程
- jconsole:查看某个Java进程中线程的运行情况(图形界面),具体操作步骤如下
五、线程运行的原理
栈与栈帧
- 每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
上下文切换
- 由一个线程切换到另一个线程即上下文切换,通俗来说,也就是要把线程运行的环境换一下,换成线程之前运行时的环境
- 专业一点就是,每次线程切换时,操作系统会保存当前线程的状态,并恢复另一个线程的状态
- 状态包括程序计数器、栈帧中的信息,如局部变量、操作数栈、返回地址等