文章目录
什么是进程
- 一个程序由指令和数据组成, 程序运行过程中, 指令要加载进CPU , 数据要加载进内存 , 过程中还需要用到磁盘, 网络等等设备.
进程就是用来加载指令, 管理内存, 管理IO的. - 当一个程序被允许, 从磁盘当中加载这个程序的代码进内存, 这样就开启了一个进程.
- 进程可以看做是容器的一个实例. 大部分程序可以同时运行多个实例(例如记事本 , 画图, 浏览器等等), 有的程序只能启动一个实例进程, 比如网易云音乐等等.
什么是线程
- 一个进程内可以分为1到多个线程
- 一个线程就是一个指令流, 将指令流中的一条条指令以一定顺寻交给CPU执行
- Java中, 线程是最小调度单位,就是执行指令由一个个线程来完成, 进程是最小的资源分配单位.
线程与进程之间的区别
- 进程基本上相互独立, 而线程存在于进程内, 是进程的一个子集
- 进程拥有共享的资源, 如内存空间, 供其内部的线程共享
- 进程间通信较为复杂
同一台计算机之间的通信称为IPC(Inter-process communication)
不同计算机之间的进程通信, 需要通过网络, 遵循共同的协议才能完成, 比如HTTP协议 - 线程通信相对简单, 因为他们共享进程内的内存, 比如多个线程可以访问一个共享变量
- 每个进程都有自己独立的地址空间, 进程之间的切换会有较大的开销; 而线程更加轻量级, 同一个进程内的线程共享进程的地址空间, 每个线程都要自己的独立的运行栈和程序计数器, 线程间切换开销小.
- 进程是操作系统资源分配的基本单位, 线程是处理器任务执行的基本单位
- 一个进程崩溃后, 在保护模式下其他进程不会被影响, 但是一个线程崩溃可能导致整个进程被操作系统杀掉, 所以多进程要比多线程健壮.
IPC协议
由于进程隔离的存在, 两个进程使用各自的虚拟地址空间, 每个进程只能访问自己虚拟地址空间.
而IPC则是进程会把内核开辟一块缓冲区, 进程把数据拷贝到内核缓冲区, 其他进程再把内核缓冲区的数据读取走.
这就是IPC机制, 进程间通信机制.
并发
单核CPU下, 线程是串行执行的, 操作系统的任务调度器将CPU的时间片分给不同的线程使用, 只是cpu在线程间的切换非常快, 人类的感觉是同时执行的, 就是微观下串行, 宏观上并行.
这种轮流使用通过时间片轮流使用CPU的做为称为并发 ,concurrent
并行
多核CPU下, 每个核都可以调度运行线程, 这就是真正的同一时刻运行多个线程
线程的应用(重点)
- 单核CPU下,多线程不能实际提高程序的运行效率, 甚至可能由于线程间平凡的上下文切换导致性能下降.
但是仍然存在意义的, 不同线程轮流使用CPU, 不会导致一个线程一直阻塞下去. - 多核CPU可以并行跑多个线程, 但是能否提高程序的运行效率还是要分情况的.
有些复杂的任务可以通过拆分, 让几个线程并行完成, 就能提高运行效率.
但是有些任务就是需要等待其他线程的结果, 那么此时多线程的存在也无实际意义. - IO操作其实不占用CPU , 只是我们拷贝文件等等的IO操作一般使用的是阻塞IO , 这是虽然线程不使用CPU, 但是需要一直等待IO结束, 没 能充分利用线程 , 此时应该使用非阻塞IO和异步IO
Java线程之创建线程的4个方法
使用Thread 对象
使用Thread对象创建一个线程, 并且重写内部的run方法
run是线程具体需要执行的方法
start是将线程交给操作系统的任务调度器, 通过时间片真正的执行这个线程.
public static void main(String[] args) {
Thread t1=new Thread("线程1"){
@Override
public void run() {
System.out.println("当前线程为"+Thread.currentThread().getName());
}
};
t1.start();
}
使用Runnable接口
Runnable是一个函数式接口, 即有且仅有一个抽象方法的接口.
通常函数式接口会加上一个@Functional注解,当你编写一个函数式接口时,如果语法错误,编译器会提示你, 且这个注解也只有这个功能.不加注解也不影响接口成为函数式接口, 只是如果语法错误不会提示.
只有使用函数式接口才能让lambda表达式正确推导
lambda表达式的使用是为了减少单个类或者匿名内部类的代码简洁度的.
同时, 匿名内部类会在打包时产生一个.class文件, 但是lambda就不会出现这种情况.
lambda对集合的迭代更加方便遍历.
配合stream流可以极大的提升开发效率.
通过Runnable接口将代码于线程分隔开, 使代码更加灵活, 比如编写好的一份run方法 , 可以在多个线程中进行复用.
Java中推荐组合优于继承 , 而Runnable与Thread就是组合与继承的关系, 使用Runable让任务类脱离了Thread的继承体系.
public static void main(String[] args) {
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println("111");
}
};
Thread thread=new Thread(runnable);
Thread thread1=new Thread(runnable);
thread.start();
}
使用Lambda简化Runable
只有一个参数时省略, 方法体只有一行代码时省略大括号
public static void main(String[] args) {
Runnable runnable= () -> System.out.println("111");
Thread thread=new Thread(runnable);
Thread thread1=new Thread(runnable);
thread.start();
}
方法体多行代码时不能省略大括号
public static void main(String[] args) {
Runnable runnable = () ->
{
System.out.println("111");
System.out.println(222);
};
Thread thread = new Thread(runnable);
Thread thread1 = new Thread(runnable);
thread.start();
}
FutureTask创建线程
FutureTask间接继承了Runable, 特点是可以接收Callable类型的参数, 用来返回一个结果.
创建出的FutureTask类可以通过调用get()方法等待内部的run方法的执行结果, 这个等待是阻塞式等待.
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask=new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(2000);
int count=0;
for (int i = 0; i < 10; i++) {
count++;
}
return count;
}
});
Thread thread=new Thread(futureTask,"线程1");
thread.start();
System.out.println(futureTask.get()); //得到结果
}
查看进程情况
linux
ps -fe 查看所有进程
ps -fT -p <PID> 查看某个进程(PID)的所有线程
kill 杀死进程
top 按大写 H 切换是否显示线程
top -H -p <PID> 查看某个进程(PID)的所有线程
windows
任务管理器可以查看进程和线程数,也可以用来杀死进程
tasklist 查看进程
taskkill 杀死进程
jps 命令查看所有 Java 进程
jstack <PID> 查看某个 Java 进程(PID)的所有线程状态
jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)