金歌 发表于 2022-8-24 17:00:50

多线程详解

1. 线程简介

程序:程序时指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
进程:执行程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程。由它自身的产生、存在和消亡的过程
线程是由进程创建的,是进程的一个实体。通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位
很多多线程是模拟出来的,真正多线程实现并行,是有多个CPU,即多核。一个CPU统一时间点只能执行一个代码,因为切换快,所以就有同时执行的错觉。 宏观并行,微观串行
public static void main(String[] args) {
    Runtime runtime = Runtime.getRuntime();
    //获取当前电脑的CPU数量
    int i = runtime.availableProcessors();
    System.out.println(i);
}2. 线程实现(重点)

2.1 实现方式一:继承Thread类

实现步骤:

[*]自定义线程类继承Thread类
[*]重写run()方法,编写线程执行体
[*]创建线程对象,调用start()方法启动线程
package com.ThreadStudy;

/**
* @ClassName ThreadDemo01
* @Description TODO 创建线程方式一:继承Thread类
* @Author Mark
* @Date 2022/6/23 13:40
* @Version 1.0
*/
//创建线程方式一:继承Thread类,重写run方法,调用start开始线程
public class ThreadDemo01 {
    public static void main(String[] args) {
      Cat cat = new Cat();    //new Cat() .var/ctrl+alt+v/alt+enter导入前半部分
      cat.start();//启动线程
      //当main线程启动一个子线程 Thread-0 主线程不会阻塞,会继续执行

      /*
      * 源码:
      * public synchronized void start() {
      *   start0();
      * }
      *
      * private native void start0();//start0是本地方法,是JVM调用,底层由C/C++实现的
      * //真正实现多线程效果由start0()实现
      * */
    }
}

//Thread实现了Runnable接口的run方法
    /*
    @Override
    public void run() {
      if (target != null) {
            target.run();
      }
    }
    */
class Cat extends Thread {//Cat继承于Thread类,该类可当作线程使用
    int times = 0;

    @Override
    public void run() {//重写run方法,写入自己的业务代码
      while (true) {
            //每隔一秒输出一次“喵喵”
            System.out.println("喵喵 " + (++times)+" 线程名:"+Thread.currentThread().getName());
            //sleep:使线程休眠 ctrl+alt+t写入try-catch,或者鼠标指向要抛出异常的方法,ctrl+alt+enter
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 80)
                break;//当times=8,线程停止
      }
    }
}进程启动--->创建main线程--->在主线程中创建一个新的子线程Thread-0
可以使用控制台jconsole打开Java监视和管理控制台查看线程执行情况,当所有的线程结束后,进程才会结束(当主线程结束,主线程的子线程还未结束,进程并不会关闭,而是等待子线程结束后关闭)
案例:多线程下载图片
package com.ThreadStudy;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;


/**
* @ClassName ThreadDemo02
* @Description TODO 练习Thread,实现多线程同步下载图片
* @Author Mark
* @Date 2022/6/23 19:09
* @Version 1.0
*/
public class ThreadDemo02 extends Thread {
    private String url;//网络地址
    private String name;//保存的文件名

    public ThreadDemo02(String url, String name) {
      this.url = url;
      this.name = name;
    }

    //下载图片线程的执行体
    @Override
    public void run() {
      WebDownloader webDownloader = new WebDownloader();
      webDownloader.downloader(url, name);
      System.out.println("下载了的文件名为:" + name);
    }

    public static void main(String[] args) {
      ThreadDemo02 thread1 = new ThreadDemo02("https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg", "fpx.jpeg");
      ThreadDemo02 thread2 = new ThreadDemo02("https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png", "mgn.jpeg");
      ThreadDemo02 thread3 = new ThreadDemo02("https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png", "edg.png");

      thread1.start();
      thread2.start();
      thread3.start();

      //下载了的文件名为:mgn.jpeg   --thread2
      //下载了的文件名为:fpx.jpeg   --thread1
      //下载了的文件名为:edg.png      --thread3
    }
}

//下载器
class WebDownloader {
    //下载方法
    public void downloader(String url, String name) {
      try {
            FileUtils.copyURLToFile(new URL(url), new File("myThread\\src\\com\\" + name));
      } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现异常");
      }
    }
}2.2 实现方式二:实现Runnable接口

java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类创建线程显然不可行了,因此我们通过实现Runnable接口来创建线程
实现步骤:

[*]定义MyRunnable类实现Runnable接口
[*]实现run()方法,编写线程执行体
[*]创建线程对象,调用start()方法启动线程
​        ThreadDemo03 threadDemo03=new ThreadDemo03();
​        new Thread(threadDemo03).start();
推荐使用Runnable对象,因为Java单继承的局限性
package com.ThreadStudy;

/**
* @ClassName TheardDemo03
* @Description TODO 创建线程方式二:实现Runnable接口
* @Author Mark
* @Date 2022/6/23 20:54
* @Version 1.0
*/
public class ThreadDemo03 {
    public static void main(String[] args) {
//      Dog dog = new Dog();
//      //dog.start;    dog不能调用start
//      //创建Thread对象,把dog对象(实现Runnable的对象)放入Thread
//      new Thread(dog).start();
//      //这里底层使用了设计模式:代理模式
      Tiger tiger = new Tiger();
      Proxy proxy = new Proxy(tiger);
      proxy.start();


    }
}

class Animal{

}

class Tiger extends Animal implements Runnable{
    @Override
    public void run() {
      System.out.println("嗷嗷");
    }
}

//线程代理类,模拟一个极简的Thread类
class Proxy implements Runnable { //这里可以把Proxy类当作Thread
    private Runnable target = null;//属性、类型是Runnable


    @Override
    public void run() {
      if (target != null) {
            target.run();//动态绑定
      }
    }

    public Proxy(Runnable target) {
      this.target = target;
    }

    public void start() {
      start0();//⭐⭐⭐⭐⭐
    }

    private void start0() {
      run();
    }
}

class Dog implements Runnable {
    int times = 0;

    @Override
    public void run() {
      while (true) {
            System.out.println("汪汪" + (++times) + " 线程名:" + Thread.currentThread().getName());

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 8) {
                break;
            }
      }
    }
}代理是为了不改变原来代码的基础上增强代码
静态代理:
package com.ThreadStudy;

/**
* @ClassName StaticProxy
* @Description TODO 静态代理
* @Author Mark
* @Date 2022/6/24 16:10
* @Version 1.0
*/
public class StaticProxy {
    public static void main(String[] args) {
      //代理是为了不改变原来代码的基础上增强代码
      //在原来只能”嘿嘿嘿“的基础上增加了”准备婚礼“和”付尾款“
//      WeddingCompany weddingCompany=new WeddingCompany(new You());//WeddingCompany代理You
//      weddingCompany.Marry();
      You you=new You();

      new Thread(()-> System.out.println("我爱你")).start();

      new WeddingCompany(you).Marry();
    }

}

//功能接口
interfaceMarry{
    //结婚方法
    void Marry();
}

//You:真实对象,实现结婚接口
class You implements Marry{
    @Override
    public void Marry() {
      System.out.println("嘿嘿嘿");
    }
}

//WeddingCompany:代理角色,帮助You
class WeddingCompany implements Marry{

    //帮助对象
    private Marry target;

    public WeddingCompany(Marry target) {
      this.target = target;
    }

    @Override
    public void Marry() {
      before();
      this.target.Marry();
      after();
    }

    private void after() {
      System.out.println("付尾款");
    }

    private void before() {
      System.out.println("准备婚礼");
    }
}方式一和方式二对比:
继承Thread类实现Runnable接口子类继承Thread类具备多线程能力实现接口Runnable具有多线程能力启动线程:子类对象.start()启动线程:线程对象(目标对象).start()不建议使用:避免OOP单继承局限性建议使用:避免单继承,灵活方便,方便同一个对象被多个线程使用案例:售票
通过继承Thread实现:
public class SellTicket {
    public static void main(String[] args) {
      SellTicket01 sellTicket01 = new SellTicket01();
      SellTicket01 sellTicket02 = new SellTicket01();
      SellTicket01 sellTicket03 = new SellTicket01();

      sellTicket01.start();
      sellTicket02.start();
      sellTicket03.start();

    }
}

class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享ticketNum

    @Override
    public void run() {
      while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                break;
            }

            //休眠50ms
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票" +
                  " 剩余票数:" + (--ticketNum));
      }
    }
}

[*]对于函数式接口,我们可以通过Lambda表达式来创建该接口的对象
推导lambda表达式:
public class SellTicket02 {
    public static void main(String[] args) {
      SellTicketWindow sellTicketWindow=new SellTicketWindow();
      new Thread(sellTicketWindow).start();
      new Thread(sellTicketWindow).start();
      new Thread(sellTicketWindow).start();
    }
}

class SellTicketWindow implements Runnable {
    private int ticketNum = 100;

    @Override
    public void run() {
      while (true) {
            if (ticketNum <= 0) {
                System.out.println("end...");
                break;
            }

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票" +
                  " 剩余票数:" + (--ticketNum));
      }
    }
}lambda简化:
public class ThreadDemo05 implements Runnable {
    private static String winner;

    @Override
    public void run() {
      for (int i = 0; i <= 100; i++) {
            //模拟兔子睡觉
            if(Thread.currentThread().getName().equals("小兔子") && i%10==0){
                try {
                  Thread.sleep(1);
                } catch (InterruptedException e) {
                  e.printStackTrace();
                }
            }

            //判断比赛是否结束
            boolean flag = gameOver(i);
            //如果比赛结束了,就停止程序
            if (flag) {
                break;
            }

            System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
      }
    }

    //判断比赛是否结束
    private boolean gameOver(int steps) {
      if (winner != null) {
            return true;
      } else if (steps >= 100) {
            winner = Thread.currentThread().getName();
            System.out.println("winner is" + winner);
            return true;
      } else {
            return false;
      }
    }

    public static void main(String[] args) {
      ThreadDemo05 threadDemo05 = new ThreadDemo05();

      new Thread(threadDemo05, "小兔子").start();
      new Thread(threadDemo05, "老乌龟").start();
    }
}3. 线程状态

https://img2022.cnblogs.com/blog/2326431/202207/2326431-20220708204845877-455761045.png
https://img2022.cnblogs.com/blog/2326431/202207/2326431-20220708204851616-497791311.png
线程方法:
方法说明void setName(String name)将此线程的名称更改为等于参数 namevoid getName()返回此线程的名称void start()导致此线程开始执行; Java虚拟机调用此线程的run方法void run()调用该Runnable对象的run方法void setPriority(int newPeiority)更改此线程的优先级void getPriority()返回此线程的优先级void join()等待这个线程死亡。static void sleep(long millis)导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停void interrupt()中断这个线程boolean isAilve()测试这个线程是否活着static void yield()暂停当前正在执行的线程对象,并执行其他线程3.1 停止线程


[*]不推荐使用JDK提供的stop()、destory()方法(@Deprecated(since="1.2"):已废弃)
[*]推荐线程自己停止下来:将线程体写到循环内,利用次数终止循环,不建议死循环
[*]建议用一个标志位进行终止变量。当flag=false,终止线程运行:通知方式
package com.ThreadCallable;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/**
* @ClassName ThreadCallableDemo
* @Description TODO 线程创建方式三:实现Callable接口
* @Author Mark
* @Date 2022/6/23 22:06
* @Version 1.0
*/
//①实现Callable接口,需要返回值类型
public class ThreadCallableDemo implements Callable {
    private String url;//网络地址
    private String name;//保存的文件名

    public ThreadCallableDemo(String url, String name) {
      this.url = url;
      this.name = name;
    }

    //②重写call方法,下载图片线程的执行体
    @Override
    public Boolean call() {
      WebDownloader webDownloader = new WebDownloader();
      webDownloader.downloader(url, name);
      System.out.println("下载了的文件名为:" + name);
      return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
      //③创建目标对象
      ThreadCallableDemo thread1 = new ThreadCallableDemo("https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg", "fpx.jpeg");
      ThreadCallableDemo thread2 = new ThreadCallableDemo("https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png", "mgn.jpeg");
      ThreadCallableDemo thread3 = new ThreadCallableDemo("https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png", "edg.png");

      //④创建执行服务:
      ExecutorService ser= Executors.newFixedThreadPool(1);

      //⑤提交执行:
      Future<Boolean> result1= ser.submit(thread1);
      Future<Boolean> result2= ser.submit(thread2);
      Future<Boolean> result3= ser.submit(thread3);

      //⑥获取结果:
      boolean rs1=result1.get();
      boolean rs2=result2.get();
      boolean rs3=result3.get();

      //⑦关闭服务:
      ser.shutdown();
    }
}

//下载器
class WebDownloader {
    //下载方法
    public void downloader(String url, String name) {
      try {
            FileUtils.copyURLToFile(new URL(url), new File("myThread\\src\\com\\" + name));
      } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现异常");
      }
    }
}
3.2 线程中断

public interface Runnable{
    public abstract void run();
}3.3 线程休眠


[*]sleep(时间)指定当前线程阻塞的毫秒数
[*]sleep存在异常InterruptedException;
[*]sleep时间达到后线程进入就绪状态
[*]sleep可以模拟网络延迟、倒计时等
[*]每个对象都有一个锁,sleep不会释放锁
package com.ThreadStudy;import java.text.SimpleDateFormat;import java.util.Date;/** * @ClassName SleepThread2 * @Description TODO 倒计时+打印系统时间 * @Author Mark * @Date 2022/6/24 21:16 * @Version 1.0 */public class SleepThread2 {    public static void main(String[] args) {//      try {//            timeDown();//      } catch (InterruptedException e) {//            e.printStackTrace();//      }      Date startTime =new Date(System.currentTimeMillis());      while(true){            try {                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));                startTime =new Date(System.currentTimeMillis());//更新当前时间                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }      }    }    //计时器//    public static void timeDown() throws InterruptedException {//      int num=10;//      while(true){//            Thread.sleep(1000);//            System.out.println(num--);//            if (num
页: [1]
查看完整版本: 多线程详解