zoukankan      html  css  js  c++  java
  • 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( ):通知

  • 相关阅读:
    FastAdmin CMS 插件下载
    使用 Python 连接到 PADS Layout
    Javascript undefined 和 null
    反馈给又拍云需要以下信息
    Web前端性能优化之图片优化
    nodejs--实现跨域抓取数据
    HTML6 展望
    cSS3 伪类:nth-child 的使用方法
    css3中的几何图形shape研究
    iScroll5 API速查随记
  • 原文地址:https://www.cnblogs.com/77-is-here/p/13079941.html
Copyright © 2011-2022 走看看