多线程

多线程

线程的三种创建方式

  1. Thread class:继承Thread类

    1. 自定义线程类继承Threadlei类

    2. 重写run()方法,编写线程执行体

    3. 创建线程对象,调用start()启动线程

      package com.thread;
      
      
      //创建线程方式1:继承Thread类,重写run方法,调用start方法开启线程
      public class ThreadTest01 extends Thread {
          @Override
          public void run() {
              for (int i = 0; i < 20; i++) {
                  System.out.println("i am watch code:" + i);
              }
          }
      
      
          public static void main(String[] args) {
              //main线程
      
              //创建一个线程对象
              ThreadTest01 threadTest01 = new ThreadTest01();
      
              //调用start()方法开启线程
              //start()方法是主线程和子线程一起执行
              //run()方法是先执行run()方法的子线程,再执行主线程
              //注意:线程开启不一定立即执行,需要等CPU的调度处理
              threadTest01.start();
      
              for (int i = 0; i < 20; i++) {
                  System.out.println("我在学习多线程:" + i);
              }
          }
      }
      

      代码实现多线程下载图片

      package com.thread;
      
      
      import org.apache.commons.io.FileUtils;
      
      import java.io.File;
      import java.io.IOException;
      import java.net.URL;
      
      //利用thread实现多线程同步下载图片
      public class ThreadTest02 extends Thread {
      
          private String url;
          private String name;
      
          public ThreadTest02(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) {
              ThreadTest02 test01 = new ThreadTest02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20190513%2F7cb9afc55171450eaa17604c5e4fc765.jpeg&refer=http%3A%2F%2F5b0988e595225.cdn.sohucs.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=030319dc87f0a7ce129ba69ab5bda5e5", "1.jpg");
              ThreadTest02 test02 = new ThreadTest02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.youxi369.com%2Farticle%2Fcontents%2F2021%2F01%2F07%2F2021010740735362.jpg&refer=http%3A%2F%2Fimg.youxi369.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=97f6180af2ab121b58f15c7f300a0ebb", "2.jpg");
              ThreadTest02 test03 = new ThreadTest02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201802%2F12%2F20180212094815_MWrv5.jpeg&refer=http%3A%2F%2Fb-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=316c717e8b3e03682f56422dfa92fba0", "3.jpg");
      
              test01.start();
              test02.start();
              test03.start();
          }
      }
      
      
      //下载器
      class webDownloader {
          public void downloader(String url, String name) {
      
              try {
                  FileUtils.copyURLToFile(new URL(url), new File(name));
      
              } catch (IOException e) {
                  e.printStackTrace();
                  System.out.println("IO异常,downloader方法出现异常");
              }
          }
      }
      

      结果:
      image

image

  下载成功,注意:要先导入commons-IO包

  验证可得:start()方法没有按程序顺序执行,先后顺序看CPU调度的执行情况
  1. Runnable接口 :实现Runnable接口

    package com.thread;
    
    
    //创建线程方式2:实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口实现类,调用start()方法
    public class ThreadTest03 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("i am watch code:" + i);
            }
        }
    
        public static void main(String[] args) {
            //创建Runnable接口实现类对象
            ThreadTest03 threadTest03 = new ThreadTest03();
    
            //创建线程对象,通过线程对象来开启我们的线程 代理
            new Thread(threadTest03).start();
    
    
            for (int i = 0; i < 20; i++) {
                System.out.println("我在学习多线程:" + i);
            }
        }
    }
    
  2. Callable接口:实现Callable接口

    package com.thread;
    
    
    import org.apache.commons.io.FileUtils;
    
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    import java.util.concurrent.*;
    
    //创建线程方式3:实现callable接口
    
    /**
     * callable的好处:
     * 1、可以定义返回值
     * 2、可以抛出异常
     */
    public class CallableTest01 implements Callable<Boolean> {
        private String url;
        private String name;
    
        public CallableTest01(String url, String name) {
            this.url = url;
            this.name = name;
        }
    
    
        //下载图片线程的执行体
        @Override
        public Boolean call() {
            webDownloader01 webDownloader01 = new webDownloader01();
            webDownloader01.downloader(url, name);
            System.out.println("下载了文件名为:" + name);
            return true;
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            CallableTest01 t1 = new CallableTest01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20190513%2F7cb9afc55171450eaa17604c5e4fc765.jpeg&refer=http%3A%2F%2F5b0988e595225.cdn.sohucs.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=030319dc87f0a7ce129ba69ab5bda5e5", "1.jpg");
            CallableTest01 t2 = new CallableTest01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.youxi369.com%2Farticle%2Fcontents%2F2021%2F01%2F07%2F2021010740735362.jpg&refer=http%3A%2F%2Fimg.youxi369.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=97f6180af2ab121b58f15c7f300a0ebb", "2.jpg");
            CallableTest01 t3 = new CallableTest01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201802%2F12%2F20180212094815_MWrv5.jpeg&refer=http%3A%2F%2Fb-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1660887492&t=316c717e8b3e03682f56422dfa92fba0", "3.jpg");
    
            //创建执行服务
            ExecutorService ser = Executors.newFixedThreadPool(3);
    
            //提交执行
            Future<Boolean> r1 = ser.submit(t1);
            Future<Boolean> r2 = ser.submit(t2);
            Future<Boolean> r3 = ser.submit(t3);
    
            //获取结果
            boolean rs1 = r1.get();
            boolean rs2 = r2.get();
            boolean rs3 = r3.get();
    
            //关闭服务
            ser.shutdownNow();
    
        }
    }
    
    
    //下载器
    class webDownloader01 {
        public void downloader(String url, String name) {
    
            try {
                FileUtils.copyURLToFile(new URL(url), new File(name));
    
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IO异常,downloader方法出现异常");
            }
        }
    }
    

并发问题

买火车票模拟代码:

package com.thread;

import java.nio.channels.NonReadableChannelException;

//多个线程操作同一个对象
//买火车票的例子
public class TreadTest04 implements Runnable {


    //火车票
    private int ticketNum = 10;


    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                break;
            }

            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->拿到了第 " + ticketNum-- + " 张票");
        }
    }

    public static void main(String[] args) {
        TreadTest04 treadTest04 = new TreadTest04();

        new Thread(treadTest04, "小明").start();
        new Thread(treadTest04, "老师").start();
        new Thread(treadTest04, "黄牛").start();
    }
}

运行结果:
image

问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱


案例:龟兔赛跑

  1. 首先来个赛道距离,然后要离终点越来越近

  2. 判断比赛是否结束

  3. 打印出胜利者

  4. 龟兔赛跑开始

  5. 要保证乌龟胜利,兔子需要睡觉,用sleep来模拟 兔子睡觉

  6. 乌龟赢得比赛

    package com.thread;
    
    public class Race implements Runnable {
    
        //胜利者
        private static String winner;
    
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
    
                //模拟兔子睡觉
                if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
    
                //判断比赛是否结束
                boolean flag = gameOver(i);
                if (flag) {
                    break;
                }
    
                System.out.println(Thread.currentThread().getName() + "--> 跑了 " + i + " 步");
            }
        }
    
        //判断比赛是否结束
        public boolean gameOver(int steps) {
            //判断是否存在胜利者
            if (winner != null) {//胜利者已经存在
                return true;
            }
            {
                if (steps == 100) {
                    winner = Thread.currentThread().getName();
                    System.out.println("winner is " + winner);
                    return true;
                }
            }
            return false;
        }
    
    
        public static void main(String[] args) {
            Race race = new Race();
    
            new Thread(race, "兔子").start();
            new Thread(race, "乌龟").start();
        }
    }
    
posted @ 2022-08-03 19:04  每年桃花开的时候  阅读(33)  评论(0)    收藏  举报