本文最后更新于 2023-09-19,文章内容可能已经过时。

1 什么是 JUC

1.1 JUC 简介

这部分建议结合JAVA基础的多线程一起看

在 Java 中,线程部分是一个重点,本篇文章说的 JUC 也是关于线程的。JUC 就是 java.util.concurrent 工具包的简称。这是一个处理线程的工具包,JDK 1.5 开始出现的。

1.2 进程与线程

  • 程序(program):为完成特定任务,用某种语言编写的一组指令的集合即指一段静态的代码,静态对象。
  • 进程(process):**程序的一次执行过程,或是正在内存中运行的应用程序。**如:运行中的QQ,运行中的网易音乐播放器。
    • 每个进程都有一个独立的内存空间,系统运行一个程序即是一个进程从创建、运行到消亡的过程。(生命周期)
    • 程序是静态的,进程是动态的
    • 进程作为操作系统调度和分配资源的最小单位(亦是系统运行程序的基本单位)系统在运行时会为每个进程分配不同的内存区域。
    • 现代的操作系统,大都是支持多进程的,支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。
  • 线程(thread)进程可进一步细化为线程,是程序内部的一条执行路径。一个进程中至少有一个线程。
    • 一个进程同一时间若并行执行多个线程就是支持多线程的。
    • 线程作为CPU调度和执行的最小单位
    • **一个进程中的多个线程共享相同的内存单元,它们从同一个堆中分配对象,可以访问相同的变量和对象。**这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患

不同的进程之间是不共享内存的。

进程之间的数据交换和通信的成本很高。

1.3 线程的状态

1.3.1 线程状态枚举类

1

Thread.State

  • **NEW(新建):**线程刚被创建,但是并未启动。还没调用start方法。
  • RUNNABLE(可运行)这里没有区分就绪和运行状态。因为对于Java对象来说,只能标记为可运行,至于什么时候运行,不是JVM来控制的了,是OS来进行调度的,而且时间非常短暂,因此对于Java对象的状态来说,无法区分。
  • Teminated(被终止)表明此线程已经结束生命周期,终止运行
  • 重点说明,根据**Thread.State的定义,阻塞状态分为三种BLOCKEDWAITINGTIMED_WAITING。**
    • BLOCKED(锁阻塞):在API中的介绍为:一个正在阻塞、等待一个监视器锁(锁对象)的线程处于这一状态。只有获得锁对象的线程才能有执行机会。
      • 比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
    • TIMED_WAITING(计时等待):在API中的介绍为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。
      • 当前线程执行过程中遇到Thread类的sleepjoin,Object类的wait,LockSupport类的park方法,并且在调用这些方法时,设置了时间,那么当前线程会进入TIMED_WAITING,直到时间到,或被中断。
    • WAITING(无限等待):在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
      • 当前线程执行过程中遇到遇到Object类的wait,Thread类的joinLockSupport类的park方法,并且在调用这些方法时,没有指定时间,那么当前线程会进入WAITING状态,直到被唤醒。
        • 通过Object类的wait进入WAITING状态的要有Object的notify/notifyAll唤醒;
        • 通过Condition的await进入WAITING状态的要有Condition的signal方法唤醒;
        • 通过LockSupport类的park方法进入WAITING状态的要有LockSupport类的unpark方法唤醒
        • 通过Thread类的join进入WAITING状态,只有调用join方法的线程对象结束才能让当前线程恢复;

说明:当从WAITING或TIMED_WAITING恢复到Runnable状态时,如果发现当前线程没有得到监视器锁,那么会立刻转入BLOCKED状态。

1.3.2 wait/sleep 的区别

(1)sleep 是 Thread 的静态方法,wait 是 Object 的方法,任何对象实例都能调用。

(2)sleep 不会释放锁,它也不需要占用锁。wait 会释放锁,但调用它的前提是当前线程占有锁(即代码要在 synchronized 中)。

(3)它们都可以被 interrupted 方法中断。

1.4 并发与并行

并行(parallel):**指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令多个CPU同时执行。**比如:多个人同时做不同的事。

2.png

并发(concurrency)指两个或多个事件在同一个时间段内发生。即在一段时间内,有多条指令单个CPU快速轮换、交替执行,使得在宏观上具有多个进程同时执行的效果。

3.png

在操作系统中,启动了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单核 CPU 系统中,每一时刻只能有一个程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

而在多核 CPU 系统中,则这些可以 并发执行的程序便可以分配到多个CPU上,实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。 目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。

要解决大并发问题,通常是将大任务分解成多个小任务, 由于操作系统对进程的调度是随机的,所以切分成多个小任务后,可能会从任一小任务处执行。这可能会出现一些现象:

• 可能出现一个小任务执行了多次,还没开始下个任务的情况。这时一般会采用队列或类似的数据结构来存放各个小任务的成果

• 可能出现还没准备好第一步就执行第二步的可能。这时,一般采用多路复用或异步的方式,比如只有准备好产生了事件通知才执行某个任务。

• 可以多进程/多线程的方式并行执行这些小任务。也可以单进程/单线程执行这些小任务,这时很可能要配合多路复用才能达到较高的效率

1.5 管程

管程(monitor)是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同一时刻只被一个进程调用(由编译器实现)

但是这样并不能保证进程以设计的顺序执行 JVM 中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程(monitor)对象,管程(monitor)会随着 java 对象一同创建和销毁执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方法在执行时候会持有管程,其他线程无法再获取同一个管程

1.6 用户线程和守护线程

用户线程:平时用到的普通线程,自定义线程

守护线程:运行在后台,是一种特殊的线程,比如垃圾回收

当主线程结束后,用户线程还在运行, JVM 存活

如果没有用户线程,都是守护线程, JVM 结束

public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " :: " + Thread.currentThread().isDaemon());
        }, "线程1");
		
       	// 如果注释掉这句 会发现主线程结束的时候 整个进程没有结束 
        thread.setDaemon(true);
        thread.start();
        System.out.println(Thread.currentThread().getName());
        Thread.sleep(3000);
    }
}