A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 小石姐姐 于 2018-4-26 10:47 编辑

多线程课程笔记

  • 多线程概念

    • 进程: Process, 一个应用程序在内存中的执行区域
    • 线程: Thread, 进程中的一条执行路径
    • 并发: 并行发生, 同时发生, 多线程就可以实现并发
    • 同步: 注意并不是同时的意思, 同步是指一步接一步的执行, 一个执行完毕再开始执行下一个. 单线程就是同步
    • 异步: 不是一步一步执行, 而是同时执行多步, 每个步骤何时结束不确定. 多线程就是异步
    • 阻塞: 上一行代码正在执行, 还没有执行完毕, 程序就阻塞在这里了, 下一行代码必须等上一行不再阻塞后才能执行


  • 单线程和多线程的特点

    • 单线程: 同一时间只做一件事, 安全性高, 效率低
    • 多线程: 同一时间做多个事情, 安全性低, 效率高


  • 多线程的实现方式

    • 继承Thread类, 重写run方法
    • 实现Runnable接口(仍然是创建Thread类对象), 重写run方法


  • java.lang.Thread类: 实现了Runnable接口

    • 构造方法

      • Thread Thread(): 创建Thead对象
      • Thread Thread(Runnable r): 通过Runnable对象创建Thread对象
      • Thread Thread(Runnable r, String threadName): 通过Runnable对象创建Thread对象并指定线程名


    • 成员方法

      • void start(): 启动线程, 即让线程开始执行run()方法中的代码
      • String getName(): 获取线程的名称
      • void setName(String name): 设置线程名称


    • 静态方法

      • static Thread currentThread(): 返回对当前正在执行的线程对象的引用
      • static void sleep(long millis): 让所在线程睡眠指定的毫秒



  • 多线程中的常见问题

    • 资源共享: 卖票问题

      • 共享资源定义位置: 共享资源要定义在多个线程能够共同使用的地方, 如:

        • 多个Thread共用同一个Runnable实现类对象, 则定义为Runnable实现类的非静态成员变量
        • 如果只用Thread子类, 则可以定义为Thread子类的静态成员变量


      • 操作共享数据的线程安全问题: 使用同步解决

        • 同步代码块

          • synchronized (锁对象) {}
          • 锁对象

            • 必须是多个线程共享的对象:

              • 一个类的Class对象
              • 如果是实现Runnable, 则可以是this




        • 同步方法

          • public (static) synchronized void method() {}
          • 锁对象

            • 静态同步方法, 锁对象是: 方法所在类的Class对象
            • 非静态同步方法, 锁对象是: this





      线程的生命周期
      • 线程的声明周期中有5种状态


      • 创建: new一个线程对象, 此时还没有调用start()
      • 就绪: 调用start()方法后, 进入就绪状态, 等待CPU执行
      • 运行: 获取了CPU的执行权, 开始运行线程
      • 阻塞: 调用了sleep(), wait(), 或由于IO操作导致阻塞. 阻塞解除后仍会返回就绪状态, 等待CPU执行
      • 销毁: 线程执行完毕





                       +---------------------------+
                       |            阻塞            |
                        +--v----------------------^-+
                           |                      |
                           |解除阻塞          被阻塞|
      new                   |                      |
    +------+             +--v---+               +--^---+               +------+
    |      |   start()   |       >-------------->      |  线程执行结束   |      |
    | 创建  >------------>  就绪 |     CPU调度    | 运行 >---------------> 销毁  |
    |      |             |       <--------------<      |               |      |
    +------+             +------+               +------+               +------+
    ```

    **线程之间的通信**

    - 使用`Object`类的成员方法
      - `void wait()`: 使当前线程处于等待状态, 并且会立刻释放锁
      - `void notify()`: 随机唤醒一个处于等待状态的线程
      - `void notifyAll()`: 唤醒所有处于等待状态的线程
      - 注意: **这三个方法必须在同步代码块中, 且只能用锁对象来调用, 否则会抛异常**
    - `sleep()`和`wait()`的区别
      - sleep
        - 让当前线程在指定时间内睡眠, 时间结束后继续执行
        - 是Thread类的静态方法
        - 不会释放锁
      - wait
        - 让当前线程等待, 直到有人唤醒
        - 是Object类的非静态方法
        - 等待会立刻释放锁
  • 注意:
  • wait的线程必须要有人notify才能唤醒, 如果所有的线程都wait了, 那程序整个就全都在等待了. 所以在写代码时必须考虑仔细


  • 线程释放锁的3种情况

    • synchronized同步代码执行完毕
    • 线程发生了异常导致线程终止
    • 线程调用了wait()方法进行等待, 等待会立刻释放锁



  • 死锁: dead lock

    • 同步代码块中的线程不出来, 也不释放锁; 同步代码块外的线程拿不到锁, 只能等在外面.


  • 发生死锁的原因:

    • 同步代码块内的线程, 可能处在死循环, IO阻塞, sleep()状态, 导致内部持有锁的线程无法出同步代码块
    • 多个线程互相持有锁又不释放锁: 两个线程执行的任务都是双层同步代码块, 每层同步都需要一个锁, 两个线程中同步代码块的锁是相反的


  • 死锁的结果: 程序卡死, 无法继续执行
  • 如何避免死锁:

    • 避免在同步代码块中执行死循环, IO阻塞操作, sleep()
    • 避免多个线程互相持有锁又不释放锁的情况



    // 嵌套同步代码块演示死锁
    public class Test {
       
        // 定义2个锁对象
        public static String lock1 = "{锁1}";
        public static String lock2 = "{锁2}";

        public static void main(String[] args) {
            // 创建2个线程对象
            MyThread1 thread1 = new MyThread1();
            MyThread2 thread2 = new MyThread2();
            thread1.setName("MyThread1");
            thread2.setName("MyThread2");
            // 开始执行
            thread1.start();
            thread2.start();
            // 注意观察控制台的小红点
        }
    }

    class MyThread1 extends Thread {

        @Override
        public void run() {
            System.out.println(getName() + "线程开始执行run()");
            // 外层同步代码块, 使用锁1
            synchronized (Test.lock1) {
                System.out.println(getName() + "线程拿到了" + Test.lock1);
                // 睡一下
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 内层同步代码块, 使用锁2
                synchronized (Test.lock2) {
                    System.out.println(getName() + "持有2把锁");
                }
            }
        }
       
    }

    class MyThread2 extends Thread {

        @Override
        public void run() {
            System.out.println(getName() + "线程开始执行run()");
            // 外层同步代码块, 使用锁2
            synchronized (Test.lock2) {
                System.out.println(getName() + "线程拿到了" + Test.lock2);
                // 睡一下
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 内层同步代码块, 使用锁1
                synchronized (Test.lock1) {
                    System.out.println(getName() + "持有2把锁");
                }
            }
        }
    }另一种加锁方式: Lock类
    • JDK5增加



    // 创建锁对象, 该锁对象也要所有线程共享唯一一个
    private Lock lock = new ReentrantLock();

    lock.lock();    // 加锁, 相当于执行到synchronized
    // 同步的代码
    lock.unlock();  // 释放锁, 相当于同步代码块执行完毕



1 个回复

正序浏览
我来占层楼啊   
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马