多线程详解

金歌  金牌会员 | 2022-8-24 17:00:50 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 712|帖子 712|积分 2136

1. 线程简介

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

2.1 实现方式一:继承Thread类

实现步骤:

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
  1. package com.ThreadStudy;
  2. /**
  3. * @ClassName ThreadDemo01
  4. * @Description TODO 创建线程方式一:继承Thread类
  5. * @Author Mark
  6. * @Date 2022/6/23 13:40
  7. * @Version 1.0
  8. */
  9. //创建线程方式一:继承Thread类,重写run方法,调用start开始线程
  10. public class ThreadDemo01 {
  11.     public static void main(String[] args) {
  12.         Cat cat = new Cat();    //new Cat() .var/ctrl+alt+v/alt+enter导入前半部分
  13.         cat.start();//启动线程
  14.         //当main线程启动一个子线程 Thread-0 主线程不会阻塞,会继续执行
  15.         /*
  16.         * 源码:
  17.         * public synchronized void start() {
  18.         *     start0();
  19.         * }
  20.         *
  21.         * private native void start0();//start0是本地方法,是JVM调用,底层由C/C++实现的
  22.         * //真正实现多线程效果由start0()实现
  23.         * */
  24.     }
  25. }
  26. //Thread实现了Runnable接口的run方法
  27.     /*
  28.     @Override
  29.     public void run() {
  30.         if (target != null) {
  31.             target.run();
  32.         }
  33.     }
  34.     */
  35. class Cat extends Thread {//Cat继承于Thread类,该类可当作线程使用
  36.     int times = 0;
  37.     @Override
  38.     public void run() {//重写run方法,写入自己的业务代码
  39.         while (true) {
  40.             //每隔一秒输出一次“喵喵”
  41.             System.out.println("喵喵 " + (++times)+" 线程名:"+Thread.currentThread().getName());
  42.             //sleep:使线程休眠 ctrl+alt+t写入try-catch,或者鼠标指向要抛出异常的方法,ctrl+alt+enter
  43.             try {
  44.                 Thread.sleep(1000);
  45.             } catch (InterruptedException e) {
  46.                 e.printStackTrace();
  47.             }
  48.             if (times == 80)
  49.                 break;//当times=8,线程停止
  50.         }
  51.     }
  52. }
复制代码
进程启动--->创建main线程--->在主线程中创建一个新的子线程Thread-0
可以使用控制台jconsole打开Java监视和管理控制台查看线程执行情况,当所有的线程结束后,进程才会结束(当主线程结束,主线程的子线程还未结束,进程并不会关闭,而是等待子线程结束后关闭
案例:多线程下载图片
  1. package com.ThreadStudy;
  2. import org.apache.commons.io.FileUtils;
  3. import java.io.File;
  4. import java.io.IOException;
  5. import java.net.URL;
  6. /**
  7. * @ClassName ThreadDemo02
  8. * @Description TODO 练习Thread,实现多线程同步下载图片
  9. * @Author Mark
  10. * @Date 2022/6/23 19:09
  11. * @Version 1.0
  12. */
  13. public class ThreadDemo02 extends Thread {
  14.     private String url;//网络地址
  15.     private String name;//保存的文件名
  16.     public ThreadDemo02(String url, String name) {
  17.         this.url = url;
  18.         this.name = name;
  19.     }
  20.     //下载图片线程的执行体
  21.     @Override
  22.     public void run() {
  23.         WebDownloader webDownloader = new WebDownloader();
  24.         webDownloader.downloader(url, name);
  25.         System.out.println("下载了的文件名为:" + name);
  26.     }
  27.     public static void main(String[] args) {
  28.         ThreadDemo02 thread1 = new ThreadDemo02("https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg", "fpx.jpeg");
  29.         ThreadDemo02 thread2 = new ThreadDemo02("https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png", "mgn.jpeg");
  30.         ThreadDemo02 thread3 = new ThreadDemo02("https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png", "edg.png");
  31.         thread1.start();
  32.         thread2.start();
  33.         thread3.start();
  34.         //下载了的文件名为:mgn.jpeg     --thread2
  35.         //下载了的文件名为:fpx.jpeg     --thread1
  36.         //下载了的文件名为:edg.png      --thread3
  37.     }
  38. }
  39. //下载器
  40. class WebDownloader {
  41.     //下载方法
  42.     public void downloader(String url, String name) {
  43.         try {
  44.             FileUtils.copyURLToFile(new URL(url), new File("myThread\\src\\com\" + name));
  45.         } catch (IOException e) {
  46.             e.printStackTrace();
  47.             System.out.println("IO异常,downloader方法出现异常");
  48.         }
  49.     }
  50. }
复制代码
2.2 实现方式二:实现Runnable接口

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

  • 定义MyRunnable类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
    ​        ThreadDemo03 threadDemo03=new ThreadDemo03();
    ​        new Thread(threadDemo03).start();
推荐使用Runnable对象,因为Java单继承的局限性
  1. package com.ThreadStudy;
  2. /**
  3. * @ClassName TheardDemo03
  4. * @Description TODO 创建线程方式二:实现Runnable接口
  5. * @Author Mark
  6. * @Date 2022/6/23 20:54
  7. * @Version 1.0
  8. */
  9. public class ThreadDemo03 {
  10.     public static void main(String[] args) {
  11. //        Dog dog = new Dog();
  12. //        //dog.start;    dog不能调用start
  13. //        //创建Thread对象,把dog对象(实现Runnable的对象)放入Thread
  14. //        new Thread(dog).start();
  15. //        //这里底层使用了设计模式:代理模式
  16.         Tiger tiger = new Tiger();
  17.         Proxy proxy = new Proxy(tiger);
  18.         proxy.start();
  19.     }
  20. }
  21. class Animal{
  22. }
  23. class Tiger extends Animal implements Runnable{
  24.     @Override
  25.     public void run() {
  26.         System.out.println("嗷嗷");
  27.     }
  28. }
  29. //线程代理类,模拟一个极简的Thread类
  30. class Proxy implements Runnable { //这里可以把Proxy类当作Thread
  31.     private Runnable target = null;//属性、类型是Runnable
  32.     @Override
  33.     public void run() {
  34.         if (target != null) {
  35.             target.run();//动态绑定
  36.         }
  37.     }
  38.     public Proxy(Runnable target) {
  39.         this.target = target;
  40.     }
  41.     public void start() {
  42.         start0();//⭐⭐⭐⭐⭐
  43.     }
  44.     private void start0() {
  45.         run();
  46.     }
  47. }
  48. class Dog implements Runnable {
  49.     int times = 0;
  50.     @Override
  51.     public void run() {
  52.         while (true) {
  53.             System.out.println("汪汪" + (++times) + " 线程名:" + Thread.currentThread().getName());
  54.             try {
  55.                 Thread.sleep(1000);
  56.             } catch (InterruptedException e) {
  57.                 e.printStackTrace();
  58.             }
  59.             if (times == 8) {
  60.                 break;
  61.             }
  62.         }
  63.     }
  64. }
复制代码
代理是为了不改变原来代码的基础上增强代码
静态代理:
  1. package com.ThreadStudy;
  2. /**
  3. * @ClassName StaticProxy
  4. * @Description TODO 静态代理
  5. * @Author Mark
  6. * @Date 2022/6/24 16:10
  7. * @Version 1.0
  8. */
  9. public class StaticProxy {
  10.     public static void main(String[] args) {
  11.         //代理是为了不改变原来代码的基础上增强代码
  12.         //在原来只能”嘿嘿嘿“的基础上增加了”准备婚礼“和”付尾款“
  13. //        WeddingCompany weddingCompany=new WeddingCompany(new You());//WeddingCompany代理You
  14. //        weddingCompany.Marry();
  15.         You you=new You();
  16.         new Thread(()-> System.out.println("我爱你")).start();
  17.         new WeddingCompany(you).Marry();
  18.     }
  19. }
  20. //功能接口
  21. interface  Marry{
  22.     //结婚方法
  23.     void Marry();
  24. }
  25. //You:真实对象,实现结婚接口
  26. class You implements Marry{
  27.     @Override
  28.     public void Marry() {
  29.         System.out.println("嘿嘿嘿");
  30.     }
  31. }
  32. //WeddingCompany:代理角色,帮助You
  33. class WeddingCompany implements Marry{
  34.     //帮助对象
  35.     private Marry target;
  36.     public WeddingCompany(Marry target) {
  37.         this.target = target;
  38.     }
  39.     @Override
  40.     public void Marry() {
  41.         before();
  42.         this.target.Marry();
  43.         after();
  44.     }
  45.     private void after() {
  46.         System.out.println("付尾款");
  47.     }
  48.     private void before() {
  49.         System.out.println("准备婚礼");
  50.     }
  51. }
复制代码
方式一和方式二对比
继承Thread类实现Runnable接口子类继承Thread类具备多线程能力实现接口Runnable具有多线程能力启动线程:子类对象.start()启动线程:线程对象(目标对象).start()不建议使用:避免OOP单继承局限性建议使用:避免单继承,灵活方便,方便同一个对象被多个线程使用案例:售票
通过继承Thread实现:
  1. public class SellTicket {
  2.     public static void main(String[] args) {
  3.         SellTicket01 sellTicket01 = new SellTicket01();
  4.         SellTicket01 sellTicket02 = new SellTicket01();
  5.         SellTicket01 sellTicket03 = new SellTicket01();
  6.         sellTicket01.start();
  7.         sellTicket02.start();
  8.         sellTicket03.start();
  9.     }
  10. }
  11. class SellTicket01 extends Thread {
  12.     private static int ticketNum = 100;//让多个线程共享ticketNum
  13.     @Override
  14.     public void run() {
  15.         while (true) {
  16.             if (ticketNum <= 0) {
  17.                 System.out.println("售票结束");
  18.                 break;
  19.             }
  20.             //休眠50ms
  21.             try {
  22.                 Thread.sleep(50);
  23.             } catch (InterruptedException e) {
  24.                 e.printStackTrace();
  25.             }
  26.             System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票" +
  27.                     " 剩余票数:" + (--ticketNum));
  28.         }
  29.     }
  30. }
复制代码

  • 对于函数式接口,我们可以通过Lambda表达式来创建该接口的对象
    推导lambda表达式:
    1. public class SellTicket02 {
    2.     public static void main(String[] args) {
    3.         SellTicketWindow sellTicketWindow=new SellTicketWindow();
    4.         new Thread(sellTicketWindow).start();
    5.         new Thread(sellTicketWindow).start();
    6.         new Thread(sellTicketWindow).start();
    7.     }
    8. }
    9. class SellTicketWindow implements Runnable {
    10.     private int ticketNum = 100;
    11.     @Override
    12.     public void run() {
    13.         while (true) {
    14.             if (ticketNum <= 0) {
    15.                 System.out.println("end...");
    16.                 break;
    17.             }
    18.             try {
    19.                 Thread.sleep(50);
    20.             } catch (InterruptedException e) {
    21.                 e.printStackTrace();
    22.             }
    23.             System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票" +
    24.                     " 剩余票数:" + (--ticketNum));
    25.         }
    26.     }
    27. }
    复制代码
    lambda简化:
    1. public class ThreadDemo05 implements Runnable {
    2.     private static String winner;
    3.     @Override
    4.     public void run() {
    5.         for (int i = 0; i <= 100; i++) {
    6.             //模拟兔子睡觉
    7.             if(Thread.currentThread().getName().equals("小兔子") && i%10==0){
    8.                 try {
    9.                     Thread.sleep(1);
    10.                 } catch (InterruptedException e) {
    11.                     e.printStackTrace();
    12.                 }
    13.             }
    14.             //判断比赛是否结束
    15.             boolean flag = gameOver(i);
    16.             //如果比赛结束了,就停止程序
    17.             if (flag) {
    18.                 break;
    19.             }
    20.             System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
    21.         }
    22.     }
    23.     //判断比赛是否结束
    24.     private boolean gameOver(int steps) {
    25.         if (winner != null) {
    26.             return true;
    27.         } else if (steps >= 100) {
    28.             winner = Thread.currentThread().getName();
    29.             System.out.println("winner is" + winner);
    30.             return true;
    31.         } else {
    32.             return false;
    33.         }
    34.     }
    35.     public static void main(String[] args) {
    36.         ThreadDemo05 threadDemo05 = new ThreadDemo05();
    37.         new Thread(threadDemo05, "小兔子").start();
    38.         new Thread(threadDemo05, "老乌龟").start();
    39.     }
    40. }
    复制代码
    3. 线程状态



    线程方法:
    方法说明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,终止线程运行:通知方式
      1. package com.ThreadCallable;
      2. import org.apache.commons.io.FileUtils;
      3. import java.io.File;
      4. import java.io.IOException;
      5. import java.net.URL;
      6. import java.util.concurrent.*;
      7. /**
      8. * @ClassName ThreadCallableDemo
      9. * @Description TODO 线程创建方式三:实现Callable接口
      10. * @Author Mark
      11. * @Date 2022/6/23 22:06
      12. * @Version 1.0
      13. */
      14. //①实现Callable接口,需要返回值类型
      15. public class ThreadCallableDemo implements Callable {
      16.     private String url;//网络地址
      17.     private String name;//保存的文件名
      18.     public ThreadCallableDemo(String url, String name) {
      19.         this.url = url;
      20.         this.name = name;
      21.     }
      22.     //②重写call方法,下载图片线程的执行体
      23.     @Override
      24.     public Boolean call() {
      25.         WebDownloader webDownloader = new WebDownloader();
      26.         webDownloader.downloader(url, name);
      27.         System.out.println("下载了的文件名为:" + name);
      28.         return true;
      29.     }
      30.     public static void main(String[] args) throws ExecutionException, InterruptedException {
      31.         //③创建目标对象
      32.         ThreadCallableDemo thread1 = new ThreadCallableDemo("https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg", "fpx.jpeg");
      33.         ThreadCallableDemo thread2 = new ThreadCallableDemo("https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png", "mgn.jpeg");
      34.         ThreadCallableDemo thread3 = new ThreadCallableDemo("https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png", "edg.png");
      35.         //④创建执行服务:
      36.         ExecutorService ser= Executors.newFixedThreadPool(1);
      37.         //⑤提交执行:
      38.         Future<Boolean> result1= ser.submit(thread1);
      39.         Future<Boolean> result2= ser.submit(thread2);
      40.         Future<Boolean> result3= ser.submit(thread3);
      41.         //⑥获取结果:
      42.         boolean rs1=result1.get();
      43.         boolean rs2=result2.get();
      44.         boolean rs3=result3.get();
      45.         //⑦关闭服务:
      46.         ser.shutdown();
      47.     }
      48. }
      49. //下载器
      50. class WebDownloader {
      51.     //下载方法
      52.     public void downloader(String url, String name) {
      53.         try {
      54.             FileUtils.copyURLToFile(new URL(url), new File("myThread\\src\\com\" + name));
      55.         } catch (IOException e) {
      56.             e.printStackTrace();
      57.             System.out.println("IO异常,downloader方法出现异常");
      58.         }
      59.     }
      60. }
      复制代码
    3.2 线程中断
    1. public interface Runnable{
    2.     public abstract void run();
    3. }
    复制代码
    3.3 线程休眠


    • sleep(时间)指定当前线程阻塞的毫秒数
    • sleep存在异常InterruptedException;
    • sleep时间达到后线程进入就绪状态
    • sleep可以模拟网络延迟、倒计时等
    • 每个对象都有一个锁,sleep不会释放锁
    [code]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
  • 本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x
    回复

    使用道具 举报

    0 个回复

    倒序浏览

    快速回复

    您需要登录后才可以回帖 登录 or 立即注册

    本版积分规则

    金歌

    金牌会员
    这个人很懒什么都没写!

    标签云

    快速回复 返回顶部 返回列表