JUC基础01——JUC概述

56 阅读10分钟

JUC概述

什么是JUC

JUC是Java并发工具包(java.util .concurrent)的缩写,是Java提供的一组用于处理多线程编程和并发操作的工具类和框架。它包含了很多有用的类和接口,用于管理并发访问、同步操作、线程池、原子操作、阻塞队列等。

JUC工具包中的一些常用功能和类库包括:

线程池:ExecutorService接口,以及它的实现类,如ThreadPoolExecutor,能方便地创建并管理线程池,提供灵活的线程调度功能。

原子类:Atomic类提供了一些原子操作,如AtomicInteger、AtomicLong等。这些原子类能保证多个线程在同一时刻对同一变量的操作不会发生冲突,从而提高并发编程的效率和安全性。

同步和锁:包括ReentrantLock、ReentrantReadWriteLock等,提供了灵活的锁机制,以控制多个线程对共享资源的访问。

条件变量:Condition接口及其实现类,如ConditionObject,提供了一种线程间的协调和通信机制,例如等待和通知机制。

阻塞队列:BlockingQueue接口及其实现类,如ArrayBlockingQueue、LinkedBlockingQueue等,是一种支持线程安全的、阻塞操作的队列,用于在多线程环境中安全地存储和访问数据。

此外,JUC中也包括一些用于管理并发访问的工具和接口,例如Semaphore(信号量)用于控制对资源的访问数量,CountDownLatch用于实现一个等待直到所有线程完成操作的功能,CyclicBarrier用于将多个线程聚集在一起,直到所有线程都达到一个特定的屏障点等

这些工具和接口可以使Java更好地处理高并发编程场景,提高程序的效率和性能

进程与线程概念

进程 :是程序的一个实例,包括正在执行的程序和程序所使用到的内存和系统资源。当一个程序被运行时,它从磁盘加载到内存,这时就开启了一个进程。每个进程都有自己独立的内存空间和系统资源,互相之间基本独立运行

线程:是操作系统能够进行运算调度的最小单位,线程包括自己的专有寄存器(如栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。线程之间共享进程内的内存和资源,所以相比于进程,线程的上下文切换成本更低,能更高效地利用CPU资源

以下是二者的主要区别:

  1. 独立性:进程基本上是相互独立的,而线程存在于进程内,是进程的一个子集。
  2. 资源拥有:进程拥有共享的资源,如内存空间,供其内部的线程共享。
  3. 通信方式:进程间通信较为复杂,不同计算机之间的进程通信,需要通过网络,遵守共同的协议,例如HTTP。线程通信相对简单,因为他们共享进程内的内存,例如多个线程可以访问同一个共享变量。
  4. 执行效率:在单核CPU下,线程实际还是串行执行的。一般会将这种线程轮流使用CPU的做法称为并发。在多核CPU下,每个核都可以调度运行线程,这时候线程是可以并行的

线程的状态

线程的状态主要可以分为以下五种:

  1. 新建状态(New):新创建了一个线程对象。

  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。

  3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

  4. 阻塞状态(Blocked):线程因为某种原因放弃CPU使用权,暂时停止运行。阻塞状态可分为三种:

    • 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。
    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
    • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
  5. 等待状态(Waiting 和time_waiting):

    • Waiting状态:线程进入Waiting状态,通常是因为调用了某些需要等待的操作,如Object.wait()或Thread.join()。在此状态下,线程会一直等待,直到另一个线程执行了notify()或unpark()操作,才能从Waiting状态进入就绪状态。
    • Time_Waiting状态:又称为计时等待状态,它与Waiting状态非常相似,但有一个时间限制。当线程进入Time_Waiting状态后,如果在指定的时间内没有接收到notify()或unpark()操作,那么线程会自动进入就绪状态。这个时间限制可以避免线程无限期地等待下去
  6. 终结状态(Terminated):线程执行完了或者因异常退出了run()方法。

上述的线程状态在 Thread.State 枚举类中定义

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

用户线程和守护线程

用户线程:平时用到的普通线程,自定义线程
守护线程:运行在后台,是一种特殊的线程,比如垃圾回收;守护线程并不属于程序中不可或缺的部分,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。守护线程也可以被用户线程创建,但它们不能转变为用户线程

创建守护线程可以通过调用线程对象的方法setDaemon(true),设置线程为守护线程。需要注意的是,必须在thread.start()之前设置

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

示例代码:

public static void main(String[] args) {
    new Thread(()->{
        System.out.println(Thread.currentThread().getName()+"::"+Thread.currentThread().isDaemon());
        while (true){

        }
    },"AA").start();
    System.out.println(Thread.currentThread().getName()+"over");
}

运行效果

image.png 当用户线程都没有了,全是守护线程,JVM运行结束 示例代码:

public static void main(String[] args) {
   Thread aa =  new Thread(()->{
        System.out.println(Thread.currentThread().getName()+"::"+Thread.currentThread().isDaemon());
        while (true){

        }
    },"AA");
    aa.setDaemon(true);//在start之前设置成为守护线程
    aa.start();
    System.out.println(Thread.currentThread().getName()+"over");
}

运行效果: image.png

管程

管程定义管程(monitor)是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同
一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行
JVM 中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程
(monitor)对象,管程(monitor)会随着 java 对象一同创建和销毁
执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方
法在执行时候会持有管程,其他线程无法再获取同一个管程。通俗的来说管程就是我们所说的

并发和并行

并发(concurrent): 指的是多个程序可以同时运行的现象,更细化的是多进程可
以同时运行或者多指令可以同时运行。但这不是重点,在描述并发的时候也不
会去扣这种字眼是否精确, 并发的重点在于它是一种现象,并发描述
的是多进程同时运行的现象
。但实际上,对于单核心 CPU 来说,同一时刻
只能运行一个线程
。所以,这里的"同时运行"表示的不是真的同一时刻有多个
线程运行的现象,这是并行的概念,而是提供一种功能让用户看来多个程序同
时运行起来了,但实际上这些程序中的进程不是一直霸占 CPU 的,而是执行一
会停一会。
要解决大并发问题,通常是将大任务分解成多个小任务, 由于操作系统对进程的
调度是随机的,所以切分成多个小任务后,可能会从任一小任务处执行。这可
能会出现一些现象:
• 可能出现一个小任务执行了多次,还没开始下个任务的情况。这时一般会采用
队列或类似的数据结构来存放各个小任务的成果
• 可能出现还没准备好第一步就执行第二步的可能。这时,一般采用多路复用或
异步的方式,比如只有准备好产生了事件通知才执行某个任务。
• 可以多进程/多线程的方式并行执行这些小任务。也可以单进程/单线程执行这
些小任务,这时很可能要配合多路复用才能达到较高的效率

并行:并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模
式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列
的长度。并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度上
则依赖于多核 CPU

Wait 和 Sleep的区别

线程状态

  • wait: 当线程调用了 Wait() 方法时,线程会进入等待状态,并释放对象的锁,允许其他线程获取锁,如果其他线程调用了同一个对象的notify()或notifyAll()方法,等待的线程将被唤醒并重新进入就绪状态。
  • sleep: 当线程调用了 Sleep() 方法时,会让线程进入休眠状态,直到休眠时间结束,在休眠期间不会释放对象的锁,其他线程无法获取当前锁。

释放锁

  • wait: 在synchronized代码块或方法中,Wait()方法会释放对象的锁,让其他线程可以进入同步代码块或方法。
  • sleep: 而Sleep()方法在休眠期间不会释放对象的锁,其他线程无法进入同步代码块或方法。这意味着使用Wait()方法可以在同步代码块或方法中进行线程间的通信,而Sleep()方法只能在同步代码块或方法外进行线程间的通信。

线程切换:

  • wait: 会阻塞当前线程,直到其他线程调用notify()或notifyAll()方法唤醒为止。唤醒后,重新竞争锁并继续执行
  • sleep: 会让出CPU执行权,并进行上下文切换,如果时间设置足够短,其他线程可以得到执行机会

方法来源

  • wait: 是Object的方法,任何实例都能调用
  • sleep: 是Thread类的静态方法

使用场景

  • wait: 常用于线程间的通信
  • sleep: 常用于让当前线程休眠或者轮循暂停操作