学习笔记-深入理解Java多线程

进程与线程

进程

  操作系统以进程为单位,分配系统资源(CPU时间片、内存等资源)的最小单位。进程就是用来加载指令、管理内存、管理IO的。

进程间的通信方式

  • 管道(pipe)及有名管道(named pipe): 管道可用于具有亲缘关系的父子进程间通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
  • 信号(signal): 信号是在软件层次上对中断机制的一中模拟,它是比较复杂的通信方式,用于通知进程有某件事发生。
  • 消息队列(message queue): 息队列是消息的链表,它克服了上两种通信方式中信号量有限的缺点,具有写权限按照一定规则向消息队列中添加消息,对消息队列有读权限可以从消息队列中获取消息。
  • 共享内存(shared memory): 它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中的共享内存数据进而更新。可以说这是进程间最有用的通信方式。
  • 信号量(semaphore): 主要作为进程之间及同一种进程的不同线程之间的同步互斥手段。
  • 套接字(scoket): 这是一种更为一般的进程间的通信机制,它可作用于网络中不同机器之间的进程间的通信,应用非常广泛。

线程

  线程有时候也被称为轻量级进程,是操作系统调度(CPU调度)执行的最小单位。线程是进程中的实体、一个进程中可以拥有多个线程,一个线程必须在一个父进程中。

线程的同步互斥

线程同步: 指线程之间具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应该是等待,直到消息到达时才会被唤醒。

线程互斥: 指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一个共享时,任务时候都最多只允许一个线程去进行访问和使用,其他线程必须进行等待。简单来说就是每次访问共享变量只能一个线程,其他线程只能等待。

线程与进程的区别

  • 进程基本上是相互独立的,而线程是存在于进程中的。
  • 进程拥有共享资源,内存空间等。供其内部线程使用其共享资源。
  • 进程的通信比较复杂。 (同一台计算机IPC,不同计算机需要通过网络协议,并共同遵守协议)
  • 线程的通信比较简单,因为他们共享进程内部的内存。
  • 线程更轻量级,线程上下文切换一般要比进程上下文切换低。

上下文切换

  上下文切换指的是一个进程或线程切换到另一个进程或线程。上下文切换只能在内核模式下发生

内核模式和用户模式

内核模式(kernel mode)

  在内核模式下,执行代码可以完全并且不受限制的访问底层硬件。他可以执行任何CPU指令和引用任务地址,是操作系统最低级别和最受信任的功能保留,如果内模式下系统崩溃,会导致整个电脑瘫痪。

用户模式(user mode)

  在用户模式下执行代码不能直接访问底层硬件或引用内存资源。在用户模式下运行代码必须委托给系统API来访问底层硬件和内存资源。因为有这一层隔离保护一旦用户模式崩溃总是可恢复的,一般运行在计算机上的大多数代码都是用户模式。

操作系统层面的线程生命周期

操作系统层面线程生命周期.png

初始化状态

  当创建完一个线程后,但是这个还不允许分配CPU执行。这种状态是属于编程语言特有的,仅仅是编程语言被创建,而对操作系统层面没有真正线程没有被创建。

就绪状态(可运行状态)

  是线程可以被CPU执行,这种状态下操作系统层面的线程已经成功的创建,所以可以分配CPU执行。

运行状态

  当空闲的CPU,操作系统会将其分配给一个可运行状态的线程,被分配到CPU状态转换为运行状态。

休眠状态

  运行状态的线程被一个阻塞API或者等待某个事件,那么线程就转换到休眠状态,同时是否CPU使用权。

终止状态

  线程执行完或者出现异常就会进入终止状态,终止状态的线程不会切换到其他线程任务状态,进入终止状态就意味着线程的生命周期结束。

Java线程详解

Java创建线程的方式

使用Thread类或继承Thread类

public static void main(String[] args) {
    // 创建线程对象
    Thread t = new Thread() {
        @Override
        public void run() {
            // 要执行的任务
        }
    };
    // 启动线程
    t.start();
}

实现Runanbe接口配合Thread

public static void main(String[] args) {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            // 需要执行的任务
        }
    };
    Thread thread = new Thread(runnable);
}

有点:

  • 把要执行的任务和线程分隔开
  • Thread代表线程,Runnable代表任务

缺点:

  • 没有返回值
  • 没有抛出异常

使用有返回值的Callable

class CallableTask implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        return new Random().nextInt();
    }
}

public void method(){
    CallableTask task = new CallableTask();
    FutureTask<Integer> futureTask = new FutureTask<>(task);
    new Thread(futureTask);
}

使用lambda

public static void main(String[] args) {
    new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
}

注意:Java创建线程的本质都是通过new Thread(); 调用Thread#start来启动线程,通过Thread#run来执行任务。

Java线程的调度机制

抢占式线程调度

  每个线程将由系统来分配执行时间,线程的切换不由线程本身决定,线程的执行时间是可控的,也不会由一个线程导致系统阻塞。

协同式线程调度

  线程的执行时间是由线程本身控制的,线程把自己的工作执行完之后,要主动的通知系统切换到下一个线程。最大的好处是实现简单,切换操作是可知的,没有线程同步的问题,缺点就是线程的执行时间是不可控的,一个线程有问题可能导致整个系统阻塞。

Java的线程调度方式

  Java的线程调度方式就是抢占式,如果希望给一些线程多分配一点时间,一些线程少分配一点时间可以设置优先级来控制。

Thread的常用方法

sleep()方法

  • 调用sleep会让当前线程从Running 进入 TIMED_WAITING状态,不会释放对象锁
  • 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出异常,并且清除中断标志。
  • 睡眠结束后的线程未必会立刻得到执行
  • sleep当传入参数为0时和yield方法相同

yield()方法

  • yield会释放CPU资源,让当前线程从Running进入到Runnable状态,让优先级更高的线程获得执行机会,不会释放对象锁
  • 如果当前只有main线程,调用yield之后,main线程会继续运行,没有比main线程更高的优先级
  • 具体的实现是依赖于操作系统的任务调度器

join()方法

  等待调用join方法结束后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景

stop()方法

  stop方法已经被JDK废弃了,原因就是stop方法太过于暴力了,强行把执行到一半的线程终止。

Java线程的生命周期

Java线程生命周期.png
Java 语言中线程共有六种状态,分别是:

  1. NEW(初始化状态)
  2. RUNNABLE(可运行状态+运行状态)
  3. BLOCKED(阻塞状态)
  4. WAITING(无时限等待)
  5. TIMED_WAITING(有时限等待)
  6. TERMINATED(终止状态)
      在操作系统层面,Java 线程中的 BLOCKED、WAITING、TIMED_WAITING 是一种状态,即前面我们提到的休眠状态。也就是说只要 Java 线程处于这三种状态之一,那么这个线程就永远没有 CPU 的使用权。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值