多线程详解02: 线程的三种创建方式 Thread类测试实例 Runnable接口测试实例 Callable接口实例测试

线程创建

线程的三种创建方式

  1. Thread class: 继承Thread类(重点)
  2. Runnable接口: 实现runnable接口(重点)
  3. Callable接口: 实现Callable接口(了解)

创建方式1:继承Thread类

  1. 自定义线程类继承Thread
  2. 重写run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法,启动线程。
public class ThreadTest1 extends Thread {
    /**run方法线程体*/
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("线程1在执行"+ i);

        }
    }

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

        //创建一个线程对象,调用start()方法开启线程
        ThreadTest1 threadTest1 = new ThreadTest1();
        
        //启动线程体
        threadTest1.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程在执行"+i);
        }
    }
}

执行结果:

主线程在执行11
主线程在执行12
线程1在执行1
线程1在执行2
主线程在执行13
主线程在执行14
主线程在执行15
主线程在执行16
线程1在执行3
主线程在执行17
线程1在执行4
线程1在执行5
线程1在执行6
线程1在执行7
线程1在执行8
线程1在执行9
线程1在执行10
线程1在执行11
线程1在执行12
线程1在执行13
线程1在执行14
线程1在执行15
线程1在执行16
线程1在执行17
线程1在执行18
线程1在执行19
主线程在执行18
主线程在执行19
主线程在执行20

可以看出主线程和线程一同时执行所以产生了交替,证明了多线程测试成功。

注意: 线程开启不一定立即执行,执行情况由CPU调度。

Thread类测试实例:多线程同时下载三个网络图片

完成此测试需要提前下载好commons-io包,可以直接百度"commons.io jar包 "进行下载. 下载完成后将该jar包放到src目录下的lib文件夹中,然后在IDEA中右击该jar包选择add as library即可.

package demo01;

import org.apache.commons.io.FileUtils;

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

/**
 *@ClassName ${NAME}
 *@Author clap of thunder
 *@Date ${DATE} ${TIME}
 *@Description 测试多线程下载网络图片:使用FileUtils工具类中的copyURLToFile方法
 **/

/**创建一个下载器类 */
class PictureDownloader{
    /**下载方法*/
    public void download(String url, String name)  {
        try {
            //FileUtils工具类中的copyURLToFile方法是把url变成一个文件
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常, download方法出现异常");
        }

    }
}

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() {
        PictureDownloader pd = new PictureDownloader();
        pd.download(url,name);
        System.out.println("下载了名为"+name+"的图片");

    }

    public static void main(String[] args) {
    //此处的url是网络上随机找的三张牛的图片
        TestThread2 t1 = new TestThread2("https://pic.baike.soso.com/p/20110302/bki-20110302134056-2107544013.jpg","第一头牛.jpg");
        TestThread2 t2 = new TestThread2("https://uploadfile.huiyi8.com/2018/af/ec/d2/4a/376129.jpg","第二头牛.jpg");
        TestThread2 t3 = new TestThread2("http://img5.taojindi.com/tc/201608/1472018730275.jpg","第三头牛.jpg");

        //代码写作的顺序是t1,2,3但是执行顺序却是由调度器安排
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

下载了名为第三头牛.jpg的图片
下载了名为第二头牛.jpg的图片
下载了名为第一头牛.jpg的图片

可在根目录下找到下载成功的三张图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-416NQIWn-1632295647990)(C:\Users\15065170282\AppData\Roaming\Typora\typora-user-images\image-20210825155615694.png)]

虽然代码写作顺序是t1,t2,t3 但是多线程的执行顺序由调度安排,不一定与代码写作顺序一致.

创建方式2: 实现runnable接口

  1. 定义一个RunnableImpl类实现Runnable接口
  2. 实现run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程: new Thread().start;
  4. 推荐使用该方式创建线程,可以避免java单继承的局限性
/**
 * @ClassName TestRunnable1
 * @Author clap of thunder
 * @Date 2021/8/25 16:10
 * @Description 测试实现Runnable接口来实现多线程
 **/
public class RunnableImpl1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("线程1在执行"+ i);

        }
    }

    public static void main(String[] args) {
        //main线程,主线程
        //创建一个Runnable实现类的对象,将其丢入Thread类执行.
        RunnableImpl1 runnableImpl1 = new RunnableImpl1();
        //创建线程对象,通过线程对象开启线程,代理
        //Thread类本身就实现了Runnable接口
        //Thread thread= new Thread(testRunnable1);
        //thread.start();
        new Thread(runnableImpl1).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程在执行"+i);
        }
    }
}

运行结果:
主线程在执行5
主线程在执行6
主线程在执行7
线程1在执行0
线程1在执行1
线程1在执行2
线程1在执行3
线程1在执行4
主线程在执行8
主线程在执行9
线程1在执行5
线程1在执行6
线程1在执行7
线程1在执行8
线程1在执行9
线程1在执行10
主线程在执行10
线程1在执行11
主线程在执行11
主线程在执行12
主线程在执行13
主线程在执行14
主线程在执行15
主线程在执行16
主线程在执行17
主线程在执行18
线程1在执行12
线程1在执行13
线程1在执行14
线程1在执行15
线程1在执行16
线程1在执行17
线程1在执行18
线程1在执行19
主线程在执行19
主线程在执行20

Runnable接口测试实例:多线程同时下载三个网络图片

package implementsRunnableInterface;
import org.apache.commons.io.FileUtils;

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

/**
 * @ClassName TestRunnable2
 * @Author clap of thunder
 * @Date 2021/8/25 16:33
 * @Description 测试多线程下载网络图片:使用FileUtils工具类中的copyURLToFile方法
 **/
public class RunnableImpl2 implements Runnable {
    private String url;
    private String name;
    public RunnableImpl2(String url, String name){
        this.url = url;
        this.name = name;
    }
    /**下载图片线程的执行体 */
    @Override
    public void run() {
        PictureDownloader pd = new PictureDownloader();
        pd.download(url,name);
        System.out.println("下载了名为"+name+"的图片");

    }

    public static void main(String[] args) {
        RunnableImpl2 t1 = new RunnableImpl2("https://pic.baike.soso.com/p/20110302/bki-20110302134056-2107544013.jpg","第一头牛.jpg");
        RunnableImpl2 t2 = new RunnableImpl2("https://uploadfile.huiyi8.com/2018/af/ec/d2/4a/376129.jpg","第二头牛.jpg");
        RunnableImpl2 t3 = new RunnableImpl2("http://img5.taojindi.com/tc/201608/1472018730275.jpg","第三头牛.jpg");

        //代码写作的顺序是t1,2,3但是执行顺序却是由调度器安排
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
    }
}
/**创建一个下载器类 */
class PictureDownloader{
    /**下载方法*/
    public void download(String url, String name)  {
        try {
            //FileUtils工具类中的copyURLToFile方法是把url变成一个文件
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常, download方法出现异常");
        }

    }
}

运行结果:

下载了名为第三头牛.jpg的图片
下载了名为第二头牛.jpg的图片
下载了名为第一头牛.jpg的图片

继承Thread类和实现Runnable接口创建多线程的对比

  • 继承Thread类

    • 子类本身具备多线程能力
    • 启动线程: 子类对象.start();
    • 不建议使用:避免OOP单继承的局限性
  • 实现Runnable接口

    • 接口Runnable的实现类具备多线程能力

    • 启动线程:传入目标对象+ Thread对象.start()

      Thread thread= new Thread(testRunnable1);
      thread.start();
      或直接
      Thread(testRunnable1).new;
    

    *建议使用:方便同一个对象被多个线程使用

案例:龟兔赛跑

下面让我们写一个龟兔赛跑的案例来巩固一下多线程的基本操作:

  1. 首先来个赛道距离, 龟兔距离终点越来越近
  2. 判断比赛是否结束
  3. 打印出胜利者
  4. 龟兔赛跑开始
  5. 故事中胜者是乌龟,兔子会睡觉所以我们来模拟兔子睡觉
  6. 最终乌龟赢得比赛
package implementsRunnableInterface;

/**
 * @ClassName RabbitTurtleRace
 * @Author clap of thunder
 * @Date 2021/9/15 21:39
 * @Description
 **/
public class RabbitTurtleRace implements Runnable{
    private static int distance = 100;

    @Override
    public void run() {

        while (distance>=0){
            int result = distance--;


            if(result==0) {
                System.out.println("比赛结束" + Thread.currentThread().getName() + "获得胜利");
                break;
            } else{
                if("rabbit".equals(Thread.currentThread().getName())&&(result%10==0)){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                    System.out.println(Thread.currentThread().getName() + "距离终点还有"+ result +"米");


                }

            }
        }


    public static void main(String[] args) {

        System.out.println("比赛开始!");
        RabbitTurtleRace race = new RabbitTurtleRace();
        new Thread(race,"rabbit").start();
        new Thread(race,"turtle").start();

    }
}

运行结果:

turtle距离终点还有10米
turtle距离终点还有9米
turtle距离终点还有8米
turtle距离终点还有7米
turtle距离终点还有6米
turtle距离终点还有5米
turtle距离终点还有4米
turtle距离终点还有3米
turtle距离终点还有2米
turtle距离终点还有1米
比赛结束turtle获得胜利
rabbit距离终点还有70

创建方式3:实现Callable接口(了解即可)

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
  5. 提交执行: Future<Boolean> result1 = ser.submit(1);
  6. 获取结果: boolean r1 = result1.get()
  7. 关闭服务:ser.shutdownNow();
  8. Callable的好处:
    1. 可以定义返回值
    2. 可以抛出异常

演示:利用Callable改造下载图片案例:

package implementsCallableInterface;

import org.apache.commons.io.FileUtils;

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

/**
 * @ClassName CallableImpl
 * @Author clap of thunder
 * @Date 2021/9/22 15:14
 * @Description
 **/
public class CallableImpl implements Callable<Boolean> {

        private final String url;
        private final String name;
        public CallableImpl(String url, String name){
            this.url = url;
            this.name = name;
        }

        @Override

        public Boolean call() {
            PictureDownloader2 pd = new PictureDownloader2();
            pd.download(url,name);
            System.out.println("下载了名为"+name+"的图片");
            return true;

        }

        public static void main(String[] args) throws ExecutionException, InterruptedException {
            CallableImpl c1 = new CallableImpl("https://pic.baike.soso.com/p/20110302/bki-20110302134056-2107544013.jpg","第一头牛.jpg");
            CallableImpl c2 = new CallableImpl("https://uploadfile.huiyi8.com/2018/af/ec/d2/4a/376129.jpg","第二头牛.jpg");
            CallableImpl c3 = new CallableImpl("http://img5.taojindi.com/tc/201608/1472018730275.jpg","第三头牛.jpg");

            //代码写作的顺序是t1,2,3但是执行顺序却是由调度器安排
            //1. 创建执行服务:
            ExecutorService ser = Executors.newFixedThreadPool(3);
            //2. 提交执行:
            Future<Boolean> result1 = ser.submit(c1);
            Future<Boolean> result2 = ser.submit(c2);
            Future<Boolean> result3 = ser.submit(c3);
            //3. 获取结果:获取的是call()的结果
            boolean r1 = result1.get();
            boolean r2 = result2.get();
            boolean r3 = result3.get();
            System.out.println(r1);
            System.out.println(r2);
            System.out.println(r3);
            //4. 关闭服务:
            ser.shutdownNow();

        }
    }


    /**创建一个下载器类 */
    class PictureDownloader2{
        /**下载方法*/
        public void download(String url, String name)  {
            try {
                //FileUtils工具类中的copyURLToFile方法是把url变成一个文件
                FileUtils.copyURLToFile(new URL(url), new File(name));
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IO异常, download方法出现异常");
            }

        }
    }


运行结果为:

下载了名为第三头牛.jpg的图片
下载了名为第一头牛.jpg的图片
下载了名为第二头牛.jpg的图片
true
true
true

匿名内部类和lambda表达式实现Runnable接口与Callable接口

public class Demo03 {
    public static void main(String[] args) {
//        匿名内部类:1.格式:new 父类或父接口(){重写方法};
//        2.要求: 必须有继承或实现关系.
//        3.本质:是一个子类对象.
//        1.Thread匿名内部类
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+i);
                }
            }
        }.start();
//        2.Runnable匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+i);
                }
            }
        }).start();

//        3.Callable匿名内部类
        new Thread(new FutureTask<String>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+i);
                }
                return "50遍";
            }
        })).start();
    }
}


            //lambda表达式形式
            //实现Runnable接口
            new  Thread(()->{
                    for (int i = 0; i < 10; i++) {
                        System.out.println(Thread.currentThread().getName()+i);
                    }
                }).start();

            //lambda表达式形式
            //实现Callable接口
            new Thread(new FutureTask<String>(
                    ()-> {for (int i = 0; i < 10; i++) {
                        System.out.println(Thread.currentThread().getName()+i);
                    }

                        return null;
                    })).start();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clap of thunder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值