JAVA多线程笔记总结

线程简介

  1. 进程与线程

    | 进程(process) | 线程(Thread) |
    | —————————————————————————————— | —————————————————————————————— |
    | 进程是程序的一次执行,是系统进行资源分配和调度的一个独立单位 | 线程作为资源调度的基本单位,是程序的执行单元,执行路径 |

  1. 线程的3个基本状态

    1. 执行

    2. 就绪

    3. 阻塞

  1. 线程的5种基本操作

    1. 创建

    2. 可运行

    3. 阻塞

    4. 调度

    5. 被终止

  1. 线程的两个基本类型

    1. 用户级线程 :管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。

    2. 系统级线程(核心级线程):由操作系统内核进行管理

  1. 何为多任务?

    生活中有很多例子,如:在开车时打电话吃零食;即一个对象同时进行多项任务。

  1. 何为多线程?

    在打游戏时,游戏中出现的视频、音频、字幕、震动反馈等都是一个个的线程,在游戏这个应用程序下分支为多个线程,而游戏就是一个进程!

Java实现多线程

  • 方法一:通过继承Thread类调用线程
  1. 继承Thread类

    public class MyThread extends Thread {
            //线程体
        @Override//重写run方法
        public void run() {
            for (int x = 0; x < 200; x++) {
                System.out.println(x);
            }
        }
    
    }
    
  1. start()方法启动线程

    public static void main(String[] args) {
            //main线程
            //TextThread01该代码的类
            //创建一个线程对象
            TextThread01 textThread01 = new TextThread01();
            //调用start()方法启动线程
            textThread01.start();
    
            for (int i = 0; i < 2000; i++) {
                System.out.println("打游戏  "+i);
            }
        }
    
  • 方法二:实现Runnable接口
  1. 实现Runnable接口

     public class TextThread03 implements Runnable {
        @Override
        public void run() {
            //run方法线程体
            for (int i = 0; i < 200; i++) {
                System.out.println("正在敲代码---"+i);
            }
        }
    
  2. 在主线程下创建Runnable接口实现类对象 ,并使用new Thread(对象名).start();启动线程

    public static void main(String[] args) {
            //创建Runnable接口实现类对象
            TextThread03 thread03 = new TextThread03();
      //启动方式一:
    //        Thread thread = new Thread();
    //
    //        thread.start();
     //启动方式二:
            new Thread(thread03).start();
        }
    
  • 方式三:实现Callable接口,需要返回值类型
  1. 实现callable接口,重写call方法

    public class Text_Callable implements Callable<Boolean> {//此处的返回值类型为布尔类型
        private String url;//网络图片地址
        private String name;
    
        public Text_Callable(String url, String name) {
            this.url = url;
            this.name = name;
        }
    
        @Override
        public Boolean call() {
            WebDownloader webDownload = new WebDownloader();
            webDownload.downloader(url, name);
            System.out.println("下载了文件名为" + name + "的文件");
            return  true;
        }
    
  2. 创建线程对象(在main方法中)

     Text_Callable t1 = new Text_Callable("https://pic2.zhimg.com/80/v2-517bd06e7b6fdaec9b9a0b3df525fc29_1440w.jpg", "1.jpg");
            Text_Callable t2 = new Text_Callable("https://pic3.zhimg.com/80/v2-c9b19e6c66d08934922ed266d71fca5d_720w.jpg", "2.jpg");
            Text_Callable t3 = new Text_Callable("https://pic4.zhimg.com/80/v2-c5eeefcb0c6c9f86553ace622a7e8329_720w.jpg", "3.jpg");
    
  3. 创建执行服务:创建线程池

    ExecutorService ser = Executors.newFixedThreadPool(3);
    
  4. 配合future接口执行服务

    Future<Boolean> r1 = ser.submit(t1);
    Future<Boolean> r2 = ser.submit(t2);
    Future<Boolean> r3 = ser.submit(t3);
    
  5. 获取结果

      boolean rs1 = r1.get();
      boolean rs2 = r2.get();
      boolean rs3 = r3.get();
    

    6.关闭服务

     ser.shutdownNow();
    

并发与并行

  • 并行:
    * 并行性是指**同一时刻**发生两个或多个事件
       * 并行是在不同的实体上的多个事件
    
  • 并发:
    * 并发性是指**同一时间间隔内**发生两个或多个事件
       * 并发是**同一实体**上的多个事件
    

Thread类源码分析

  1. 设置线程名

    Thread.currentThread().getName()
    
  2. 守护线程

    thread.setDaemon(true);//默认为false,表示用户线程
    
            thread.start();//启动守护线程
    
            new Thread(you).start();//启动用户线程
    
  3. 优先级线程

    调用Thread.currentThread().getPriority()获得优先级线程的级别(int)

    创建MyPriority实现runnable接口

    class MyPriority implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
        }
    }
    

    new一个MyPriority对象,创建线程

    MyPriority myPriority = new MyPriority();
    Thread t2 = new Thread(myPriority);
    

    先设置优先级再启动线程

    t2.setPriority(1);//线程优先级范围1-10
            t2.start();
    
  4. 线程生命周期

    • 线程休眠(Thread.sleep())

      调用sleep方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态

      //模拟延时
                 try {
                     Thread.sleep(200);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
      
    • 线程礼让(Thread.yield())

  • 让当前执行的线程执行但不阻塞,若让CPU重新调度,不一定会礼让

    此时线程由运行态变为就绪态

      public class TextYield {
          public static void main(String[] args) {
              MyYield myYield = new MyYield();
              new Thread(myYield,"a").start();//线程a
    
              new Thread(myYield,"b").start();//线程b
          }
      }
      class MyYield implements Runnable{
          @Override
          public void run() {
              System.out.println(Thread.currentThread().getName()+"线程开始执行");
              Thread.yield();//线程礼让
              System.out.println(Thread.currentThread().getName()+"线程停止执行");
          }
      }
    
  • 线程霸占(Thread.join())

    • java合并线程(即插队),其他线程阻塞,调用join方法,会等待该线程执行完毕后再执行别的线程
    public class TextJoin implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 200; i++) {
                System.out.println("线程vip来了"+i);
            }
        }
        //vip线程霸占主线程
        public static void main(String[] args) throws InterruptedException{
            //main线程
            TextJoin textJoin = new TextJoin();
            Thread thread = new Thread(textJoin);
            thread.start();
    
            for (int i = 0; i < 200; i++) {
                if(i == 100){
                    thread.join();//插队
                }
                System.out.println("main"+i);
            }
        }
    }
    
  • interrupt方法

  • 我们一般使用的是interrupt来请求终止线程~

    • 要注意的是:interrupt不会真正停止一个线程,它仅仅是给这个线程发了一个信号告诉它,它应该要结束了(明白这一点非常重要!)
    • 也就是说:Java设计者实际上是想线程自己来终止,通过上面的信号,就可以判断处理什么业务了。
    • 具体到底中断还是继续运行,应该由被通知的线程自己处理
    Thread t1 = new Thread( new Runnable(){
        public void run(){
            // 若未发生中断,就正常执行任务
            while(!Thread.currentThread.isInterrupted()){
                // 正常任务代码……
            }
            // 中断的处理代码……
            doSomething();
        }
    } ).start();
    

线程同步

同步方法

public synchronized void method(int args){}
  • synchronized方法都必须获得该方法的对象的锁才能执行,否则线程会出现阻塞
  • 方法一旦执行就会独占该锁,指导该方法返回才释放锁

同步块

synchronized (obj){}
  • obj称为同步监视器
  • obj可以是任何对象但推荐使用共享资源作为同步监视器

死锁(DeadLock)

造成死锁的原因可以概括成三句话:

  • 当前线程拥有其他线程需要的资源
  • 当前线程等待其他线程已拥有的资源
  • 都不放弃自己拥有的资源

1.1 锁顺序死锁

 Makeup(int choice,String girlname){
        this.choice=choice;
        this.girlname=girlname;
    }
    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //化妆:互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if (choice == 0){
            synchronized (lipstick){
                System.out.println(this.girlname+"获得口红的锁");
                Thread.sleep(1000);
                synchronized (mirror){
                    System.out.println(this.girlname+"获得镜子的锁");
                }
            }
        }else {
            synchronized (mirror){
                System.out.println(this.girlname+"获得镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick){
                    System.out.println(this.girlname+"获得口红的锁");
                }
            }
        }
    }

1.2 避免死锁的方法

避免死锁可以概括成三种方法:

  • 固定加锁的顺序(针对锁顺序死锁)
  • 开放调用(针对对象之间协作造成的死锁)
  • 使用定时锁—>tryLock()
    • 如果等待获取锁时间超时,则抛出异常而不是一直等待

1.3线程池

线程池可以看做是线程的集合。在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不是销毁)。这样就实现了线程的重用