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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 李皓321 初级黑马   /  2018-4-23 15:39  /  599 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 小石姐姐 于 2018-4-25 15:49 编辑

day11多线程

多线程:
进程:当前正在运行的程序,一个应用程序在内存中的执行区域。
线程:进程中的一个执行控制单元,执行路径
一个进程可以有一个线程,也可以有多个线程。
单线程:安全性高,但是效率低
多线程:安全性低,效率高。
扩展:
      并发:并行发生,同时发生,多线程就可以实现并发
      同步:sync,注意并不是同时的意思,同步是指一步接一步的执行,一个
                执行完毕在开始执行下一个。
                          单线程就是同步。
      异步:async,不是一步一步执行,而是同时执行多步,每个步骤何时结束
                不确定
                            多线程就是异步。
      同一个线程内的代码是同步执行的,不同线程的代码是异步执行的。
      阻塞:上一行代码正在执行,还没有执行完毕,程序就阻塞在这里了,下
                 一行代码必须等上一行代码执行完毕后才能执行。
创建线程的第一种方式:继承Thread类
    java.lang.Thread类:实现了Runnable接口
    构造方法:
           Thread  Thread():创建Thread对象
           Thread Thread(runnable  r):通过Runnable对象创建Thread对象
           Thread  Thread(Runnable  r ,String threadName):通过Runnable对
                                                        象创建Thread对象并指定线程名
    成员方法:
            void  run():用于让子类重写,表示该线程要执行的任务,不能直接
                                    调用
            void  start():启动线程,即让线程开始执行run()方法中的代码
            String getName():获取线程名称。
            void  setName():设置线程名称。
    静态方法:
            static Thread currentThread():返回当前正在执行的线程对象的引
                                                                  用
            static void sleep (long millis):让所在线程睡眠指定的毫秒
多线程实现方式1:
     定义类, 继承Thread类, 重写 run() 方法
步骤
1. 定义一个类, 继承Thread类
2. 在子类中重写父类的 run() 方法
3. 创建子类的对象
4. 调用子类对象的 start() 方法启动线程
说明:
        一个Thread对象, 就相当于一条线程. 一个类继承了 Thread 类, 该
                类对象就可以作为一个线程去单独执行任务
       run() 方法中的代码, 就是该线程要执行的任务
       如果想要多个线程同时执行相同的任务, 只需要定义一个类, 然后创
                 建该类的多个对象来启动即可
       如果想要多个线程同时执行不同的任务, 则需要定义多个类, 分别重
                 写run方法, 然后创建这些类的对象来启动。
            注意:
线程何时启动?
           线程对象调用 start() 方法开始启动, 启动后在新的线程中执行
                   run() 方法的代码
线程何时结束?
           当 run() 方法中的代码执行完毕后, 线程就会自动结束并销毁.
如果 run() 方法中有死循环无法结束, 则该线程永远执行
不要调用 stop() 或 suspend() 方法来结束线程, 这两个方法不管用, 不安全, 而且已经过时
main()方法是单线程的;
       main()方法
                  也是执行在一个线程中的,该线程的名称称为main,一般称为主线程,主线程和其他线程没什么区别
                   所以main()方法是单线程的,方法中的代码是一行一行向下执行的
创建线程的第二种方式:实现Runnable接口:
        java.lang.Runnable接口;可将其对象看作是子线程要运行的任务
                   抽象方法:
                               void run()
                   Runnable 接口出现的原因
                               因为JAVA只支持单继承,如果继承了Thread类,就无法继承其他类,局限性太大,所以使用实现Runable接口方式,既能继承其他类,还能实现多个接口,不会影响类的扩展性
         多线程实现方式2:
实现Runnable接口, 重写 run() 方法
步骤
1. 定义一个类, 实现 Runnable 接口
2. 在子类中重写父类的 run() 方法
3. 创建一个子类的对象
4. 创建 Thread 对象, 在其构造方法中传入刚才创建的子类的对象
5. 调用 Thread 对象的 start() 方法启动一个线程
注意:
这种方式仍然是通过多个 Thread 对象来启动多个线程
多个Thread对象使用同一个 Runnable 实现类对象, 就可以执行相同的任务

Thread类:
静态方法:
          static void sleep (long millis):让所在线程睡眠指定毫秒
  多线程中的线程安全问题: 使用同步代码块解决共享资源问题:
          同步:
  •      表示多个线程在执行同步的代码时,这些线程只能按顺序一个一个 去执行。
  • 优缺点:


                   同步安全性高,效率低
                   不同步安全性低,效率高
  • 使用synchronized关键字:


                  同步代码块:使用synchronized修饰的代码块儿。
  •   作用:


  • 被同步的代码块儿包裹的代码相当于在一个房间内,锁对象相当于房间的锁。
  • 一个线程进入同步代码块,会把门锁上,等他执行完毕,开门下一个才能继续操作。


2,应用位置:
         那些代码属于共享操作,需要避免安全问题,就将这些代 码放入同步代码块中。
     3,术语:
               线程进入同步代码块   获取锁
               线程出代码代码块儿    释放锁
  •     同步方法:使用syncchronized修饰的方法
  • 锁对象


                      锁是一个任意类型的对象
                            Object对象可以, String对象可以, Student对象等也可以...
  • 锁对象必须是被所有线程共享的, 也就是说多个线程都共用一个锁


如使用同一个Runnable对象中的成员变量
或使用某个类的静态变量
或使用某个类的字节码对象
  • 同步必须配合锁对象来使用


多线程常见安全问题:
   1,对于操作了共享数据的代码是否同步。
   2,同步使用的是否是同一个锁对象。
多线程中的线程安全问题:使用同步方法解决   
  • 同步方法:


                1,synchronized 修饰的方法
                  作用:
                        该方法在同一时间只能被一个线程执行, 多个线程需要按顺序一
                       个一个调用方法
                2, 同步方法的锁对象:
同步方法也有锁对象
静态同步方法:
                           是方法所在类的 Class 对象, 也就是该类的字节码对象
                3,非静态同步方法:
                           是 this , 也就是该方法所在类的对象
  • 同步代码块和同步方法如何选择?


                  如果一段代码的功能是独立的, 会在多个地方重复执行, 且需要同步,
                 可以定义为同步方法, 便于复用。   
如果你发现运行几次多线程代码后电脑越来越卡, 看一下Eclipse控制台上的小小显示器按钮, 点击右侧三角, 这里都是控制台运行的程序, 如果没有写 <terminated> , 则说明你的多线程程序可能没有结束, 仍然在运行, 点击红色方块结束
继承Thread类和实现Runnable接口在共享资源上有什么区别:
  • 继承Thread类


                 1,Thread类中的非静态成员变量是每个线程各自拥有的, 不能作为
                        共享资源
                 2,静态成员变量是所有线程对象共享的, 可以作为共享资源
  • 实现Runnable接口, 如果为所有Thread对象都传入同一个Runnable对象, 则


                 1,Runnable对象中的非静态成员变量会被所有Thread对象(也就是
                       所有线程)共享, 可以作为共享资源
                  2,Runnable对象中的静态成员变量也会被所有Thread对象共享,
                       也可以作为共享资源
  • 综上所述


                  1,继承Thread类方式下, 共享资源要定义为Thread类的静态资源
                  2,实现Runnable接口方式下, 共享资源要定义为Runnable接口的
                     静态或非静态资源, 且所有Thread对象都要传入同一个Runnable
                     对象。
实现多线程的第3种方法: 创建线程池 thread pool:
方法3:  Executors 线程池+ Callable 实现类
  • 定义类, 实现 Callable 接口, 重写call()方法, 注意Callable接口有泛型,


             call()方法要有泛型类的返回值.
  • 使用 Executors 类创建线程池, 并提交 Callable 实现类启动任务


                   3种创建线程池的方式, 返回线程池实现类:
                          1,static ExecutorService newCachedThreadPool() : 创建新
                                的线程池, 为每个新传入的任务创建一个线程
                          2,static ExecutorService newFixedThreadPool(int
                                 nThreads) : 创建固定数量线程的线程池(常用)
                          3,static ExecutorService newSingleThreadExecutor() : 创建
                                只有一个线程的线程池
  • 线程启动和销毁, 使用 ExecutorService 对象的成员方法


                1,Future submit(Callable<T> task) : 提交线程, 让线程池线程运行
                     起来, 一个返回值的任务
                2,Future submit(Runnable task) : 同上, 只是参数不同
                3,shutdown() : 通知线程执行完任务后关闭. 注意该方法不会在调用
                    时立刻停止线程, 而是让线程执行完任务后停止. 如果不调用此方
                    法, 则线程任务执行完后仍然在运行
  • 创建几个线程就创建几个实现类对象并执行几个submit, submit会返回


            Future类的线程运行结果.
  • 这种方法创建的线程只在最初创建一次, 执行完毕后回到线程池中等待, 除非调用线程池的shutdown方法关闭线程池才会销毁, 节省资源且效率高


线程的生命周期
线程的声明周期中有5中状态:
  •      创建:new一个线程对象,此时还没有调用start();
  •        就绪:调用Start()方法后,进入就绪状态,等待CPU执行
  •         运行:获取了CPU的执行权,开始运行线程
  •         阻塞 :调用了sleep(),wait(),或由于IO操作导致阻塞,阻塞


                  接触后仍返回就绪状态等CPU执行。
  •        销毁: 线程执行完毕


线程之间的通信
  • 使用 Object 类的成员方法


1,void wait() : 使当前线程处于等待状态, 并且会立刻释放锁
2,void notify() : 随机唤醒一个处于等待状态的线程
3,void notifyAll() : 唤醒所有处于等待状态的线程
4,注意: 这三个方法必须在同步代码块中, 且只能用锁对象来调用, 否则会抛异常
  • sleep() 和 wait() 的区别


           1,sleep
  • 让当前线程在指定时间内睡眠, 时间结束后继续执行
  • 是Thread类的静态方法
  • 不会释放锁


          2,wait



    • 让当前线程等待, 直到有人唤醒
    • 是Object类的非静态方法
    • 等待会立刻释放锁


  • 注意:


wait的线程必须要有人notify才能唤醒, 如果所有的线程都wait了, 那程序整个就全都在等待了. 所以在写代码时必须考虑仔细
线程释放锁的3种情况
1. synchronized同步代码执行完毕
2. 线程发生了异常导致线程终止
3. 线程调用了 wait() 方法进行等待, 等待会立刻释放锁
常见线程问题: 死锁
  • 死锁: dead lock


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


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


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

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马