线程的三种创建方式


前言

本片文章介绍了如何创建线程以及线程的常用方法。


一、什么是线程?

线程:简单的说,就是计算机在做一件事
单线程:在计算机中同一时间只能做一件事
多线程:在计算机中同一时间可以做多件事
其实多线程在我们的生活中的使用场景很多,比如网盘的上传下载,12306的多窗口售票等等
它的主要好处有:1. 减少队列阻塞带来的影响 2. 提高CPU的利用率

二、创建线程的方式

1.继承Thread类

多线程的创建方式一:继承Thread类
1、定义一个子类继承线程类java.lang.Thread,重写run()方法
2、创建子类的对象
3、调用子类对象的start()方法启动线程(底层会自动去执行run方法)

方式一优缺点:
优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。

代码如下:

/*
多线程
    让程序同时做多件事

多线程的创建方式一:继承Thread类
    1. 定义一个子类继承线程类java.lang.Thread,重写run()方法
    2. 创建子类的对象
    3. 调用子类对象的start()方法启动线程(底层会自动去执行run方法)

优缺点
    优点:编码简单
    缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。

注意事项
    1、启动线程必须是调用start方法,不是调用run方法。
    2、直接调用run方法会当成普通方法执行,只有调用start方法才是启动一个新的线程执行。
    3、不要将主线任务放在start方法之前,这样主线程一直是先跑完的,相当于是一个单线程的效果了。

扩展
    对于单核cpu来讲, 多线程是一种假象
*/
public class Demo1 {
    public static void main(String[] args) {
        //需求:创建两个线程,分别用于打印10个A和10个B,最后观察下输出顺序
        AThread aThread = new AThread();
        BThread bThread = new BThread();

        aThread.start();
        bThread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("C"+i);
        }
    }
}
class AThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("A"+i);
        }
    }
}
class BThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("B"+i);
        }
    }
}

2.实现Runnable接口

多线程的创建方式二:实现Runnable接口
1、定义一个线程任务类实现Runnable接口,重写run()方法
2、创建任务类对象
3、把任务类对象交给Thread处理
4、调用线程对象的start()方法启动线程

方式二的优缺点:
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点:需要多一个Runnable对象。

代码如下:


/*
多线程的创建方式二:实现Runnable接口
    1. 定义一个线程任务类实现Runnable接口,重写run()方法
    2. 创建任务类对象
    3. 把任务类对象交给Thread处理
        public Thread(Runnable target)
    4. 调用线程对象的start()方法启动线程

优缺点
    优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
    缺点:需要多一个Runnable对象。
*/
public class Demo1 {
    public static void main(String[] args) {
        //需求:创建两个线程,分别用于打印10个A和10个B,最后观察下输出顺序
        aThread aThread = new aThread();
        bThread bThread = new bThread();
        Thread threadA = new Thread(aThread);
        Thread threadB = new Thread(bThread);
        threadA.start();
        threadB.start();

    }

}
class aThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("A"+i);
        }
    }
}
class bThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("B"+i);
        }
    }
}

这种方式还有第二种写法(匿名内部类):
1、可以创建Runnable的匿名内部类对象。
2、再交给Thread线程对象。
3、再调用线程对象的start()启动线程。
代码如下:


public class Demo2 {
    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("A"+i);
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("B"+i);
            }
        }).start();
    }
}

注意:
以上两种写法都有一个问题:假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。

那怎么解决这个问题呢?

JDK 5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)。
这种方式最大的优点:可以返回线程执行完毕后的结果。

3.实现Callable接口

多线程的第三种创建方式:利用Callable接口、FutureTask类来实现。
1、创建任务对象
定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。
把Callable类型的对象封装成FutureTask(线程任务对象)。
2、把线程任务对象交给Thread对象。
3、调用Thread对象的start方法启动线程。
4、线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。

线程创建方式三的优缺点:
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。
代码如下:


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/*
多线程的创建方式三:实现Callable接口
    1. 创建任务对象
        定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据
        把Callable类型的对象封装成FutureTask(线程任务对象)
            public FutureTask<>(Callable call)	把Callable对象封装成FutureTask对象
            public V get() throws Exception	获取线程执行call方法返回的结果
    2. 把线程任务对象交给Thread对象。
    3. 调用Thread对象的start方法启动线程。
    4. 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果

优缺点
    优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果
    缺点:编码复杂一点。
*/
public class Demo1 {

    public static void main(String[] args) throws Exception {
        //需求:启动两个子线程,分别计算100之内的奇数的和和偶数的和,然后在主线程中再做个汇总,得到总和
        JiCall jiCall = new JiCall();
        OuOddCall ouOddCall = new OuOddCall();
        FutureTask<Integer> jiFutureTask = new FutureTask<>(jiCall);
        FutureTask<Integer> ouFutureTask = new FutureTask<>(ouOddCall);
        Thread jiThread = new Thread(jiFutureTask);
        Thread ouThread = new Thread(ouFutureTask);
        jiThread.start();
        ouThread.start();
        Integer integer = jiFutureTask.get();
        Integer integer1 = ouFutureTask.get();
        System.out.println(integer+integer1);
    }
}
class JiCall implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i+=2) {
             sum +=i;
        }
        return sum;
    }
}
class OuOddCall implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 2; i <= 100; i+=2) {
            sum +=i;
        }
        return sum;
    }
}


三、Thread类的常用方法

Thread提供的常用方法:

public String getName() 获取当前线程的名称,线程名称默认是Thread-索引
public void setName(String name) 为线程设置名称
public static Thread currentThread() 获取当前执行的线程对象
public static void sleep(long time) 让当前执行的线程休眠多少毫秒后,再继续执行
public final void join()… 让调用当前这个方法的线程先执行完!

代码如下:


/*
Thread常见构造器 
    public Thread(String name)              可以为当前线程指定名称
    public Thread(Runnable r)               封装Runnable对象成为线程对象
    public Thread(Runnable r, String name)  封装Runnable对象成为线程对象,并指定线程名称

程对象的常见方法
    public void run()                     线程的任务方法
    public void start()                   启动线程
    public String getName()               获取当前线程的名称,线程名称默认是Thread-索引
    public void setName(String name)      为线程设置名称
    public static Thread currentThread()  获取当前执行的线程对象

    public static void sleep(long time)   让当前执行的线程休眠多少毫秒后,再继续执行
    public final void join()              让调用当前这个方法的线程先执行完
*/
public class Demo1 {

    //需求: 通过下面任务,我们来学习sleep和join方法
    //1. 创建A、B两个线程类,分别打印1~5
    //2. 在主线程序中创建A、B两个子线程
    //3. 在主线程序中开启A、B两个子线程
    //4. 让A线程每打印一次,都要暂停3秒钟
    //5. B线程要等A线程打印完毕,再开始打印
    public static void main(String[] args) throws InterruptedException {
        aThread aThread = new aThread();
        bThread bThread = new bThread();
        aThread.setName("线程一");
        bThread.setName("线程二");
        aThread.start();
        aThread.join();
        bThread.start();

    }

}

class aThread extends Thread{
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        for (int i = 1; i < 6; i++) {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(thread.getName()+i);
        }
    }
}
class bThread extends Thread{
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        for (int i = 1; i < 6; i++) {
            System.out.println(thread.getName()+i);
        }
    }
}

Thread类还提供了诸如:yield、interrupt、守护线程、线程优先级等线程的控制方法,在开发中很少使用,这些方法会后续需要用到的时候再讲解。

总结

三种线程的创建各有优缺点,所以请选择合适的方式创建线程。

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值