复习多线程

目录

线程简介:

线程创建(三种):

方法一: Thread类 

方法二: 创建Runnable类:

线程并发: 

龟兔赛跑:

方法三:实现Callable接口


线程简介:

任务, 进程, 线程, 多线程: 程序是静态的, 程序跑起来变成了进程, 进程里分为了若干个线程.

注意: 很多多线程是模拟出来的, 真正的多线程是指有多个cpu, 即多核, 如服务器. 如果是模拟出来的多线程, 即在一个cpu的情况下, 在同一个时间点, cpu只能执行一个代码, 因为切换的很快, 所以就有同时执行的错觉.

  • 线程就是独立的执行路径;
  • 在程序运行时, 即使没有自己创建的线程, 后台也会有多个线程, 如主线程, gc线程;
  • main()称之为主线程, 为系统的入口, 用于执行整个程序
  • 在一个进程中, 如果开辟了多个线程, 线程的运行由调度器(cpu)安排调度, 调度器是与操作系统紧密相关的, 先后顺序是不能认为的干预的
  • 对同一份资源操作时, 会存在资源抢夺的问题, 需要加入并发控制
  • 线程会带来额外的开销, 如cpu调度时间, 并发控制开销
  • 每个线程在自己的工作内存交互, 内存控制不当会造成数据不一致.

线程创建(三种):

  • Thread class : 继承Thread类 ( 重点 ), 实现了Runnable接口
  • Runnable接口 : 实现Runnable接口 ( 重点 )
  • Callable接口 : 实现Callable接口 ( 了解 )

方法一: Thread类 

  • 自定义线程类继承Thread类
  • 重写run()方法, 编写线程执行体
  • 创建线程对象, 调用start()方法启动线程
//创建线程方式一: 继承Thread类, 重写run()方法, 调用start开启线程
public class TestThread1 extends Thread{

    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我再看代码---" + i);
        }
    }

    public static void main(String[] args) {
        //main线程, 主线程

        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();
        //调用start()方法开启线程
        testThread1.start();
        //调用run方法会先执行完run, 再执行main方法.
        /*testThread1.run();*/

        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程---" + i);
        }
    }
}
输出结果:

我再看代码---0
我在学习多线程---0
我在学习多线程---1
我在学习多线程---2
我在学习多线程---3
我在学习多线程---4
我在学习多线程---5
我在学习多线程---6
我再看代码---1
我在学习多线程---7
我再看代码---2
我在学习多线程---8
我再看代码---3
我在学习多线程---9
我在学习多线程---10
我在学习多线程---11
我在学习多线程---12
我在学习多线程---13
我在学习多线程---14
我在学习多线程---15
我再看代码---4
我在学习多线程---16
我再看代码---5
我在学习多线程---17
我再看代码---6
我再看代码---7
我再看代码---8
我再看代码---9
我再看代码---10
我再看代码---11
我再看代码---12
我在学习多线程---18
我在学习多线程---19
我再看代码---13
我再看代码---14
我再看代码---15
我再看代码---16
我再看代码---17
我再看代码---18
我再看代码---19

 练习(lib导入commons-io-2.11.0.jar):  下载网络图片

//练习Thread, 实现多线程同步下载图片
public class TestThread2 extends Thread{

    private String url;//网络图片地址
    private String name;//保存的文件名

    public TestThread2(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) {
        TestThread2 t1 = new TestThread2("https://t12.baidu.com/it/u=949912871,2851013736&amp;fm=58", "1.jpg");
        TestThread2 t2 = new TestThread2("https://p.ssl.qhimg.com/dmsmty/148_148_100/t0155a138b0754de130.webp", "2.jpg");
        TestThread2 t3 = new TestThread2("https://p.ssl.qhimg.com/dmsmty/148_148_100/t01241afac55d126121.webp", "3.jpg");

        t1.start();
        t2.start();
        t3.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方法出现问题");
        }
    }
}

输出 : (说明为同时进行)

下载了文件名为3.jpg
下载了文件名为2.jpg
下载了文件名为1.jpg

方法二: 创建Runnable类:

  • 定义MyRunnable类实现Runnable接口
  • 实现run()方法, 编写线程执行体
  • 创建线程对象, 调用start()方法启动线程
//创建线程方式2 : 实现runnable接口, 重写run方法,
//执行线程需要丢入runnable接口实现类,调用start方法.
public class TestThread03 implements Runnable {
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我再看代码---" + i);
        }
    }

    public static void main(String[] args) {
//        // 创建一个Runnable接口实现类对象
        TestThread03 testThread03 = new TestThread03();
//        //创建线程对象, 通过线程对象来开启我们的线程 : 代理
//        Thread thread = new Thread();
//        thread.start();
        //简写
        new Thread(testThread03).start();

        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程---" + i);
        }
    }
}
结果:

我在学习多线程---0
我再看代码---0
我在学习多线程---1
我再看代码---1
我在学习多线程---2
我再看代码---2
我在学习多线程---3
我再看代码---3
我在学习多线程---4
我再看代码---4
我在学习多线程---5
我再看代码---5
我在学习多线程---6
我再看代码---6
我在学习多线程---7
我再看代码---7
我在学习多线程---8
我再看代码---8
我再看代码---9
我再看代码---10
我再看代码---11
我再看代码---12
我再看代码---13
我再看代码---14
我再看代码---15
我再看代码---16
我再看代码---17
我再看代码---18
我在学习多线程---9
我在学习多线程---10
我再看代码---19
我在学习多线程---11
我在学习多线程---12
我在学习多线程---13
我在学习多线程---14
我在学习多线程---15
我在学习多线程---16
我在学习多线程---17
我在学习多线程---18
我在学习多线程---19

练习.plus : 下载网络图片

//改为Runnable, 实现多线程同步下载图片
public class TestThread2 /*extends Thread*/implements Runnable{

    private String url;//网络图片地址
    private String name;//保存的文件名

    public TestThread2(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) {
        TestThread2 t1 = new TestThread2("https://t12.baidu.com/it/u=949912871,2851013736&amp;fm=58", "1.jpg");
        TestThread2 t2 = new TestThread2("https://p.ssl.qhimg.com/dmsmty/148_148_100/t0155a138b0754de130.webp", "2.jpg");
        TestThread2 t3 = new TestThread2("https://p.ssl.qhimg.com/dmsmty/148_148_100/t01241afac55d126121.webp", "3.jpg");
//        t1.start();
//        t2.start();
//        t3.start();
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).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方法出现问题");
        }
    }
}

小结 :

  • 继承Thread类
    • 子类继承Thread类具备多线程能力
    • 启动线程: 子类对象.start()
    • 不建议使用: 避免OOP单继承局限性
  • 实现Runnable接口
    • 实现接口Runnable具有多线程能力
    • 启动线程: 传入目标对象+Thread对象.start()
    • 推荐使用: 避免单继承局限性, 灵活方便, 方便同一个对象被多个线程使用

线程并发: 

//多个线程同时操作同一个对象
//买火车票的例子

//发现问题: 多个线程操作同一个资源的情况下, 线程不安全, 数据紊乱
public class TestThread4 implements Runnable {

    //票数
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true){
            if (ticketNums<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "票");
        }
    }

    public static void main(String[] args) {
        TestThread4 ticket = new TestThread4();

        new Thread(ticket, "小明").start();
        new Thread(ticket, "黄牛").start();
        new Thread(ticket, "工人").start();
    }
}

结果:

工人-->拿到了第10票
黄牛-->拿到了第9票
小明-->拿到了第10票
工人-->拿到了第8票
小明-->拿到了第7票
黄牛-->拿到了第7票
工人-->拿到了第6票
黄牛-->拿到了第5票
小明-->拿到了第4票
工人-->拿到了第3票
小明-->拿到了第2票
黄牛-->拿到了第2票
工人-->拿到了第1票
小明-->拿到了第0票
黄牛-->拿到了第-1票

龟兔赛跑:

  1. 首先来个赛道距离,然后要离重点越来越近
  2. 判断比赛是否结束
  3. 打印出胜利者
  4. 龟兔赛跑开始
  5. 故事中是乌龟赢,兔子需要睡觉,所以我们来模拟兔子睡觉
  6. 终于,乌龟赢得比赛
//模拟龟兔赛跑
public class Race implements Runnable {

    //胜利者
    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子")&& i%20==0){
                try {
                    Thread.sleep(2);
                } 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;
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race, "兔子").start();
        new Thread(race, "乌龟").start();
    }
}
结果:

乌龟--->跑了0步
乌龟--->跑了1步
乌龟--->跑了2步
乌龟--->跑了3步
乌龟--->跑了4步
乌龟--->跑了5步
乌龟--->跑了6步
乌龟--->跑了7步
乌龟--->跑了8步
乌龟--->跑了9步
乌龟--->跑了10步
乌龟--->跑了11步
乌龟--->跑了12步
乌龟--->跑了13步
乌龟--->跑了14步
乌龟--->跑了15步
乌龟--->跑了16步
乌龟--->跑了17步
乌龟--->跑了18步
乌龟--->跑了19步
乌龟--->跑了20步
兔子--->跑了0步
兔子--->跑了1步
兔子--->跑了2步
兔子--->跑了3步
兔子--->跑了4步
兔子--->跑了5步
兔子--->跑了6步
兔子--->跑了7步
兔子--->跑了8步
兔子--->跑了9步
兔子--->跑了10步
兔子--->跑了11步
兔子--->跑了12步
兔子--->跑了13步
乌龟--->跑了21步
乌龟--->跑了22步
乌龟--->跑了23步
乌龟--->跑了24步
乌龟--->跑了25步
乌龟--->跑了26步
乌龟--->跑了27步
乌龟--->跑了28步
兔子--->跑了14步
兔子--->跑了15步
兔子--->跑了16步
兔子--->跑了17步
兔子--->跑了18步
兔子--->跑了19步
乌龟--->跑了29步
乌龟--->跑了30步
乌龟--->跑了31步
乌龟--->跑了32步
乌龟--->跑了33步
乌龟--->跑了34步
乌龟--->跑了35步
乌龟--->跑了36步
乌龟--->跑了37步
乌龟--->跑了38步
乌龟--->跑了39步
乌龟--->跑了40步
乌龟--->跑了41步
乌龟--->跑了42步
乌龟--->跑了43步
乌龟--->跑了44步
乌龟--->跑了45步
乌龟--->跑了46步
乌龟--->跑了47步
乌龟--->跑了48步
乌龟--->跑了49步
乌龟--->跑了50步
乌龟--->跑了51步
乌龟--->跑了52步
乌龟--->跑了53步
乌龟--->跑了54步
乌龟--->跑了55步
乌龟--->跑了56步
乌龟--->跑了57步
乌龟--->跑了58步
乌龟--->跑了59步
乌龟--->跑了60步
乌龟--->跑了61步
乌龟--->跑了62步
乌龟--->跑了63步
乌龟--->跑了64步
乌龟--->跑了65步
乌龟--->跑了66步
乌龟--->跑了67步
乌龟--->跑了68步
兔子--->跑了20步
兔子--->跑了21步
兔子--->跑了22步
兔子--->跑了23步
乌龟--->跑了69步
兔子--->跑了24步
兔子--->跑了25步
乌龟--->跑了70步
兔子--->跑了26步
乌龟--->跑了71步
兔子--->跑了27步
乌龟--->跑了72步
乌龟--->跑了73步
乌龟--->跑了74步
乌龟--->跑了75步
乌龟--->跑了76步
兔子--->跑了28步
乌龟--->跑了77步
兔子--->跑了29步
乌龟--->跑了78步
兔子--->跑了30步
乌龟--->跑了79步
兔子--->跑了31步
乌龟--->跑了80步
兔子--->跑了32步
兔子--->跑了33步
兔子--->跑了34步
乌龟--->跑了81步
兔子--->跑了35步
乌龟--->跑了82步
兔子--->跑了36步
乌龟--->跑了83步
兔子--->跑了37步
乌龟--->跑了84步
兔子--->跑了38步
乌龟--->跑了85步
乌龟--->跑了86步
乌龟--->跑了87步
乌龟--->跑了88步
乌龟--->跑了89步
乌龟--->跑了90步
乌龟--->跑了91步
乌龟--->跑了92步
乌龟--->跑了93步
兔子--->跑了39步
乌龟--->跑了94步
乌龟--->跑了95步
乌龟--->跑了96步
乌龟--->跑了97步
乌龟--->跑了98步
乌龟--->跑了99步
winner is 乌龟

方法三:实现Callable接口

  • 实现Callable接口, 需要返回值类型
  • 重写call方法, 需要抛出异常
  • 创建目标对象
  • 创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
  • 提交执行: Future<Boolean> result1 = ser.submit(t1);
  • 获取结果: boolean r1 = result1.get()
  • 关闭服务: ser.shutdownNow();

练习.plus++: 下载网络图片

//线程创建方式三: 实现callable接口
public class TestCallable implements Callable<Boolean> {

    private String url;//网络图片地址
    private String name;//保存的文件名

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

    @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 {
        TestCallable t1 = new TestCallable("https://t12.baidu.com/it/u=949912871,2851013736&amp;fm=58", "1.jpg");
        TestCallable t2 = new TestCallable("https://p.ssl.qhimg.com/dmsmty/148_148_100/t0155a138b0754de130.webp", "2.jpg");
        TestCallable t3 = new TestCallable("https://p.ssl.qhimg.com/dmsmty/148_148_100/t01241afac55d126121.webp", "3.jpg");
        //创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行: Future<Boolean> result1 = ser.submit(t1);
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);
        //获取结果: boolean r1 = result1.get()
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();
        //打印返回值
        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
        //关闭服务: ser.shutdownNow();
        ser.shutdownNow();
    }
}

//下载器
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方法出现问题");
        }
    }
}
结果:
下载了文件名为1.jpg
下载了文件名为3.jpg
下载了文件名为2.jpg
true
true
true

总结: Callable的好处:

  1. 可以定义返回值
  2. 可以抛出异常
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值