多线程基础总结

140 阅读5分钟

多线程基础

为什么会有多线程?本质原因与分布式产生原因是一致的。本质原因是摩尔定律的失效摩尔定律:cpu主频每隔24个月就会翻一倍。早期业务处理的增量没有硬件性能增长快。早期计算机大部分是单核的。21世纪的到来,整个数据要处理的量。已经远远超过了我们当前计算机的处理能力。在单个服务器内集成越来越多的cpu、这样就可以让单个服务器有更高的处理能力。多cpu核心意味着同时操作系统有更多的并行计算资源可以使用。操作系统以线程为基本的调度单元。

当我们启动一个进程时,这个进程里面至少有一个或者多个线程。进程用来管理所有的资源。cpu 上划分多个时间片,所有的线程再去抢这些时间片。每个时间点一个cpu、只有一个时间片被线程抢到,谁抢到谁运行。本质在单核时代,并发情况要比多核情况下要简单的多。为什么? -->在多核时代,一个服务器上有多个cpu核心,那么这多个cpu核心,大家时间片是完全没有关系的。在并发时间点,会有多个线程并行。会涉及到复杂的资源抢占,特别是内存资源的抢占。导正应用程序的复杂性。

cpu 内核架构
  • SMP架构(共享内存)
    • 一块内存由4个cpu通过总线共享访问

image.png

  • NUMA架构
    • cpu 分治访问,如两个cpu访问同一块内存。这样容易扩展。对单内存cpu竞争减少。router 跨cpu 实现不同cpu 之间的通信

image.png

Java线程的创建过程

调用 Thread.start() 方法,进入到JVM层面,在JVM层面,会把Java的Thread对象,转换为操作系统的真实的线程对象。JVM 调用操作系统的API,创建了一个操作系统的线程、先为线程分配JVM的栈、相关的内存,准备好了之后,就会启动这个线程。执行run方法中的代码逻辑 。通过管理这个操作系统的生命周期(实际由JVM统一管理),实现了多线程程序。

Java Thread 使用示例
public static void main(String[] args) {
    Runnable task = () -> {
        try {
            TimeUnit.SECONDS.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread thread = Thread.currentThread();
        System.out.println("当前线程 " + thread);
    };
    Thread thread = new Thread(task);
    thread.setName("test-thread-1");
    thread.setDaemon(true);
    thread.start();
}

按照我们正常的思维来说:这段代码应该输出的是 test-thread-1,但是事与愿违。JVM 中所有的线程都是守护线程的时候,jvm 会 提前终止。这段代码输出为空。当新启的线程不是守护线程的时候,JVM 不会终止掉这个进程。

常用方法汇总
  • Thread
重要属性与方法说明
volatile String name线程名称-诊断分析使用
boolean daemon是否为守护线程,决定JVM 优雅的关闭
Runnable target线程任务,只能通过构造方法传入
synchronized void start()启动新线程,并执行run里面重载的方法
void join()等待某个线程执行完毕(汇合结果)
currentThread()静态方法,获取当前线程信息
sleep线程睡眠并让出等待时间
  • Object
wait & notify (统一协调调度的重要手段)说明
wait()放弃锁 + 等待 0 ms + 尝试获取锁
native wait(long)放弃锁 + wait + 到时间自动唤醒/中途被唤醒 + 尝试获取锁
native notify发送信号 通知一个等待线程
native notifyAll发送信号 通知所有等待线程
  • slepp & wait

sleep ,释放cpu 、但不会释放锁。 wait : 释放对象锁。

  • 方法对应状态改变
方法状态
sleepTIMED_WAITING , 不会释放锁,给其他线程执行机会的最佳方式
yield当前线程调用此方法,当前线程放弃所持有的cpu时间片 ,但不释放任何资源,由RUNNING -> 就绪 状态,让 操作系统继续选择。该方法与 slepp 类似,但是不能由用户控制暂停时间。如果cpu不是很忙碌,他会立马再次进入 运行状态。
join在当前线程里调用其他线程的join方法,线程进入 WAITING/TIMED_WAITING 状态,当前线程不会释放对象锁,由于Join 本身是 wait ,所以会释放 其他线程所持有的对象锁
wait释放锁,进入等待队列,依靠 notify 或 notifyAll 方法唤醒,或者等待时间到了自动唤醒。
notify将单个线程调用的wait或多个线程调用的wait 给唤醒 ,选择是任意性的。notifyAll 唤醒 obj monitor 上等待的所有的线程
  • Thread 异常中断与处理
  1. 内部自己处理,不throws到外层。(future 例外)
  2. wait、sleep、join 三个方法可能会抛出 interrupt 异常,在该线程被阻塞的时候调用interrupt() 会抛出。 如果线程没有被阻塞interrupt()方法没有任何效果,但是后续调用 阻塞方法则会直接跑出 异常。
  3. 计算密集型可以采用异常打断的方法100万次大循环 可以拆分两层循环,在大循环外面sleep 或者 设计 全局变量维护 抛异常或者全局变量改变的时候,计算可以退出,或者做一些其他操作。