Java并发编程:基础-进程与线程

1.进程与线程

1.1 进程与线程

进程

  • 运行程序必须将程序指令加载到CPU,程序数据加载至内存,进程就是来加载指令,管理内存和IO的。
  • 程序被运行相当于开启了进程,进程就是程序的实例,实际中的exe类型文件

线程

  • 进程由多个线程组成
  • 一个线程就是一个指令流,最终执行指令的还是线程
  • Java中,CPU从线程中获取指令

区别:

  • 进程之间相互独立
  • 进程内的线程共享一个内存空间
  • 进程间通信分为两种:
    • 同一计算机进程间通信:IPC(Inter-process Communication)
    • 不同计算机进程间通信:需要网络,遵守协议,比如http
  • 线程通信相对简单,上下文切换成本低(上下文切换:内存不够时,把一些暂时不用的代码暂停,切换到其他代码进行)

1.2 并行与并发

单核 cpu 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感 觉是 同时运行的 。

总结为一句话就是:微观串行,宏观并行,

一般会将这种 线程轮流使用 CPU 的做法称为并发, concurrent

2. Java线程

2.1 创建线程

匿名内部类

相当于简化的继承或实现接口,不必再重新创建一个类,直接在接口后跟{方法的实现}

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running with anonymous class");
    }
};
new Thread(runnable).start();

lambda表达式

对匿名内部类的简化,

Runnable runnable = () -> System.out.println("Running with Lambda");
new Thread(runnable).start();
//也可以直接写为
new Thread(() ->{ 
    System.out.println("Running with Lambda")
}).start();

2.2 查看进程线程方法

  • windows 任务管理器可以查看进程和线程数,也可以用来杀死进程, tasklist 查看进程, taskkill 杀死进程
  • linux ps -fe 查看所有进程, ps -fT -p 查看某个进程(PID)的所有线程,kill 杀死进程, top 按大写 H 切换是否显示线程, top -H -p 查看某个进程(PID)的所有线程
  • Java jps 命令查看所有 Java 进程 ,jstack 查看某个 Java 进程(PID)的所有线程状态, jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

2.3 线程运行

栈与栈帧

每个线程启动后,虚拟 机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存

  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

  • 执行过程

  • 在这里插入图片描述

  • 执行结束后逐渐释放掉内存

线程上下文切换(Thread Context Switch)

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

2.4 常见方法

start与run方法

run方法,不能异步,还是main方法调用

public static void main(String[] args) {
 Thread t1 = new Thread("t1") {
 @Override
 public void run() {
 log.debug(Thread.currentThread().getName());
 FileReader.read(Constants.MP4_FULL_PATH);
        }
    };
 
 t1.run();
 log.debug("do other things ...");
}

改为t1.start,就可以异步了

sleep与yield

  • sleep,在哪被调用就让哪个线程休眠
    • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
    • 其它线程可以使用interrupt方法打断正在睡眠的线程,这时 sleep 方法会抛出InterruptedException异常
    • 睡眠结束后的线程未必会立刻得到执行
    • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
  • yield,让出cpu使用权
    • 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
    • 具体的实现依赖于操作系统的任务调度器,有时想让但让不出去
  • 线程优先级,在cpu忙的时候可能会有用,空闲时几乎没用
  • sleep应用:在循环中加sleep防止cpu占满

join

等待线程运行结束,谁调用就等谁运行结束。

可以用于线程同步
来自于黑马程序员

有时限的join,如果线程提前结束,join也会结束。

Interrupt

  • 打断阻塞
    • 打断后会抛出异常,会清空打断状态,即打断状态清空为false
  • 打断正常
    • 打断状态为true
  • 打断park,不会清空打断状态,如果已经是true,则park会失效,Interrupted方法会将打断标记设为false,使park生效

2.5 不推荐方法

stop()、suspend()、resume(),会让锁得不到释放

2.6 守护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守 护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束

log.debug("开始运行...");
 Thread t1 = new Thread(() -> {
 log.debug("开始运行...");
 sleep(2);
 log.debug("运行结束...");
 }, "daemon");
 // 设置该线程为守护线程
t1.setDaemon(true);
 t1.start();
 sleep(1);
 log.debug("运行结束...");
  • 垃圾回收器线程就是一种守护线程

  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等 待它们处理完当前请求

2.7 线程状态

五种状态

在这里插入图片描述

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 【运行状态】指获取了 CPU 时间片运行中的状态
    • 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】
    • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入 【阻塞状态】
    • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑 调度它们
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

java六种状态

在这里插入图片描述

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的 【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为 是可运行)
  • BLOCKED , WAITING , 详述 TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节
  • TERMINATED 当线程代码运行结束
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值