JavaSE基础入门_014_多线程

多线程

什么是进程

  • 正在运行的程序,是系统进行资源分配的基本单位。

  • 目前操作系统都是支持多进程,可以同时执行多个进程,通过进程ID区分。

  • 单核CPU在同一时刻,只能运行一个进程;宏观并行、微观串行

 

什么是线程

  • 线程,又称轻量级进程。进程中的一条执行路径,也是CPU的基本调度单位。

  • 一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。

 

进程和线程的区别

  1. 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。

  2. 一个程序运行后至少有一个进程。

  3. 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。

  4. 进程间不能共享数据段地址,但同进程的线程之间可以。

 

线程的组成

  • 任何一个线程都具有基本的组成部分:

    • CPU时间片:操作系统(OS)会为每个线程分配执行时间。

    • 运行数据:

      • 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。

      • 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。

    • 线程的逻辑代码。

 

线程的特点

  • 线程抢占式执行

    • 效率高

    • 可防止单一线程长时间独占CPU

  • 在单核CPU中,宏观上同时执行,微观上顺序执行。

 

创建线程

  • 创建线程三种方式:

    1. 继承 Thread 类,重写 run 方法

    2. 实现 Runnable 接口

    3. 实现 Callable 接口

创建线程(1)

  • 第一种方式:继承 Thread

    1. 新建子类继承 Thread

    2. 覆盖 run 方法

    3. 创建子类对象

    4. 调用 start() 方法

获取和修改线程名称

  • 获取线程ID和线程名称

    1. 在 Thread 的子类中调用 this.getId() 或 this.getName()

    2. 使用 Thread.currentThread().getId() 和 Thread.currentThread().getName()

  • 线程 Id 是系统自动分配的, 不允许修改.

  • 修改线程名称:

    1. 调用线程对象的setName() 方法

    2. 使用线程子类的构造方法赋值

 

创建线程 (2)

  • 实现 Runnable 接口:

    1. 新建 实现类 实现 Runnable 接口

    2. 覆盖 run() 方法

    3. 创建实现类对象

    4. 创建线程对象

    5. 调用 start() 方法

 

线程的状态(基本)

  • New 初始状态:线程对象被创建,即为初始状态。只在堆中开辟内存,与常规对象无异。

    • 调用start()进入就绪态

  • Ready 就绪状态:调用start() 之后,进入就绪状态。等待OS选中,并分配时间片。

    • OS选中,分配时间片进入 运行态

  • Running 运行状态:获得时间片之后,进入运行状态,如果时间片到期,则回到就绪状态。

    • 时间片到期,回到 就绪态

    • run() 运行结束,进入 终止状态

  • Terminated 终止状态:主线程 main() 或独立线程 run() 结束,进入终止状态,并释放持有的时间片。

 

线程的常见方法

  • 休眠:

    • public static void sleep(long millis) //参数:毫秒数
    • 当前线程主动休眠 millis 毫秒。

  • 放弃:【又叫避让,当前线程主动放弃 CPU 使用权,给别的线程抢到CPU机会】

    • public static void yield()
    • 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。

  • 加入:【非静态方法,只能通过对象来调用;在线程启动之后再加】

    • public final void join()
    • 允许其他线程加入到当前线程中。【直到加入线程运行完毕,才执行当前线程】

  • 优先级:

    • 线程对象 . setPriority()

    • 线程优先级为 1-10,数字越大优先级越高,默认为5,优先级越高,表示获取CPU机会越多。

  • 守护线程:

    • 在线程启动之前完成设置

    • 线程对象. setDaemon( true );设置为守护线程

    • 线程有两类:用户线程(前台线程)、守护线程(后台线程):为前台线程提供服务。

    • 如果程序中所有前台线程都执行完毕了,后台线程会自动结束

    • 垃圾回收器线程属于守护线程

 

线程的状态(等待)

  • 当前线程被执行 sleep( 等待时间 ) 指令后,进入Timed Waiting 限期等待。等待时间 结束进入 就绪状态

  • 当前线程执行 join() 指令后,进入 Waiting 无限期等待。

 

线程安全问题

  • 多线程安全问题:

    • 当多线程并发访问 临界资源【共享资源】时,如果破坏原子操作,可能会造成数据不一致。

    • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。

    • 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

  • 解决方式:同步

 

同步方式(1)

  • 同步代码块

    • synchronized ( 临界资源对象 ) { //对临界资源对象加锁,注意这个参数必须是一个对象类型,这是个锁
      // 代码 (原子操作)
      }
    • 注:

      • 每个对象都有一个互斥锁标记,用来分配给线程的

      • 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。

      • 线程退出同步代码块时,会释放相应的互斥锁标记。

 

同步方式(2)

  • 同步方法

    • synchronized 返回值类型 方法名称( 形参列表0 ) { //对当前对象(this)加锁
      // 代码 (原子操作)
      }
    • 注:

      • 只有拥有对象互斥锁标记的线程, 才能进入该对象加锁的同步方法中.

      • 线程退出同步方法时, 会释放相应的互斥锁标记.

 

同步规则

  • 注意:

    • 只有在调用包含同步代码块的方法, 或者同步方法时, 才需要对象的锁标记.

    • 如调用不包含同步代码块的方法, 或普通方法时, 则不需要锁标记, 可直接调用.

  • 已知 JDK 中线程安全的类:

    • StringBuffer

    • Vector

    • Hashtable

    • 以上类中的公开方法,均为 synchronized 修饰的同步方法.

 

线程的状态(阻塞)

  • 运行态 进入 同步操作后,没有拿到锁,进入阻塞状态(Blocked)

  • 阻塞状态(Blocked)拿到锁后,回到 就绪状态(Ready)

  • JDK5 之后, 就绪、运行统称为 Runnable

 

经典问题 (1)

  • 死锁:

    • 当第一个线程拥有 A 对象锁标记, 并等待 B 对象锁标记, 同时第二个线程拥有 B 对象锁标记, 并等待 A 对象锁标记时, 产生死锁.

    • 一个线程可以同时拥有多个对象的锁标记, 当线程阻塞时, 不会释放已拥有的锁标记, 由此可能造成死锁.

 

线程通信

  • 等待: [ 通过锁对象调用的方法 ]

    • public final void wait()
      public final void wait(long timeout)
    • 必须在对 obj 加锁的同步代码块中. 在一个线程中, 调用 obj.wait() 时, 此线程会释放其拥有的所有锁标记. 同时此线程阻塞在 obj 的等待队列中. 释放锁, 进入等待队列.

  • 通知:

    • public final void notify()
      public final void notifyAll()
    • 其他线程调用 notify() 方法来唤醒 等待的线程.

 

经典问题 (2)

  • 生产者、消费者:

    • 若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区【一般用数组或集合实现】,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。

 

总结

  • 线程的创建:

    • 方式1:继承 Thread

    • 方式2:实现 Runnable 接口(一个任务Task),传入给Thread对象并执行。

 

  • 线程安全:【多个线程访问临界资源】锁是引用对象,必须唯一

    • 同步代码块:为方法中的局部代码(原子操作)加锁。

    • 同步代码:为方法中的所有代码(原子操作)加锁。

 

  • 线程间的通信:

    • wait( )/ wait( long timeout ): 等待

    • notify( ) / notifyAll( ):通知

原文地址:https://www.cnblogs.com/77-is-here/p/13079941.html