[重识多线程] 01- java多线程创建

一、什么是线程

进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源

操作系统在分配资源时是把资源分配给进程的,但是CPU资源比较特殊,它是被分配到线程的,因为真正要占用CPU运行的是线程,所以也说线程是CPU分配的基本单位

以Java 为例,我们启动一个main函数时,实际上就是启动了一个JVM 的进程main函数所在的线程就是这个进程的一个线程,也称为主线程。一个JVM进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域

img

二、从内存角度看线程

内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM 内存布局规定了 Java 在运行过程中内存申请、分配、管理的策略 ,保证了 JVM 的高效稳定运行。

如果按照线程是否共享来分类的话,如下图所示:

image-20200614114132144

三、线程创建和运行

Thread类:

package java.lang;
public class Thread implements Runnable {
	// 构造方法
	public Thread(Runnable target);
	public Thread(Runnable target, String name);
	
	public synchronized void start();
}

Runnable 接口

package java.lang;

@FunctionalInterface
public interface Runnable {
    pubic abstract void run();
}

3.1 继承Thread类

其实现继承关系图为:Thread 实现了Runnbale接口,并重写了Run

image.png

其中target是一个Runnable对象,使用继承的方式target为null不会调用此方法

image.png

继承的创建线程:

public class ThreadDemo01 extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("MyThread");
    }

    public static void main(String[] args) {
        ThreadDemo01 thread1 = new ThreadDemo01();
        thread1.start();

    }
}

我们自己的MyThread线程类继承Thread,重写run方法

3.2 继承Thread匿名内部类的写法

public class ThreadDemo2 {

    public static void main(String[] args) {
        // 初始化线程实例
        new Thread() {
            @Override
            public void run() {
                System.out.println(getName() + " is running");
            }
        }.start();
    }

}


这种方式主要是Thread接口的构造函数的区分,当target不为空的时候,会执行匿名内部类里面的run方法

image.png

3.3 实现Runnable方法

image.png

基于两个参数的构造函数:

public class CreateThreadDemo3{
    public static void main(String[] args) {
        RunnableThreadTest runnableThreadTest = new RunnableThreadTest();
        new Thread(runnableThreadTest, "线程1").start();
        new Thread(runnableThreadTest, "线程2").start();
    }

}

/**
 * 实现Runnable接口的方式
 */
class RunnableThreadTest implements Runnable{

    private int i = 0;

    @Override
    public void run() {
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()  + " is running: " + i);
        }
    }
}

3.4 实现Runable接口匿名内部类的方式

public class ThreadDemo2 {
    public static void main(String[] args) {
        // 初始化线程实例
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类...");
            }
        });
        thread.start();
    }
}

简化java8的形式

public class ThreadDemo2 {
    public static void main(String[] args) {
        // 初始化线程实例
        Thread thread = new Thread(() -> System.out.println("匿名内部类..."));
        thread.start();
    }
}

3.5 继承Callable接口创建线程

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:call()方法可以有返回值,可以声明抛出异常。

public class ThreadDemo5 {
    public static void main(String[] args) {
        CallableTest callableTest = new CallableTest();
        FutureTask futureTask = new FutureTask<>(callableTest);
        new Thread(futureTask).start();
        try {
            System.out.println("子线程返回值:" + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class CallableTest implements Callable{

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        System.out.println(Thread.currentThread().getName() + "running: " + sum);
        return sum;
    }
}

image.png

Java5提供了Future接口来接收Callable接口中call()方法的返回值。
Callable接口是 Java5 新增的接口,不是Runnable接口的子接口,所以Callable对象不能直接作为Thread对象的target。

针对这个问题,引入了RunnableFuture接口,RunnableFuture接口是Runnable接口和Future接口的子接口(下图所示),可以作为Thread对象的target (实现RunnableFuture接口的子类可以作为Thread的target)。同时,Java5提供了一个RunnableFuture接口的实现类:FutureTask ,FutureTask可以作为Thread对象的target。

image.png

看一下FutrueTask的实现,可以看出FutureTask即支持实现Callable接口的子类,也支持Runnable接口的子类,并且当传过来的是Runnable接口的时候,会执行Executors的callable方法转化为callable接口的,执行的。

image.png

至于Future接口的原理,在后面的文章中再作分析,现在只是知道线程的创建方式。

四、创建线程的三种方式的对比

4.1 实现Runnable/Callable接口相比继承Thread类的优势

总体来说一般的业务代码中都是采用实现Runnable接口或者是Callable接口

  1. 可以避免java中单继承的限制
  2. 适合多个线程进行资源共享
  3. 线程池只能放入Runable或Callable接口实现类,不能直接放入继承Thread的类

4.2 Callable和Runnable的区别

  1. Callable重写的是call()方法,Runnable重写的方法是run()方法
  2. call()方法执行后可以有返回值,run()方法没有返回值
  3. call()方法可以抛出异常,run()方法不可以
  4. 运行Callable任务可以拿到一个Future对象,表示异步计算的结果 。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果

参考文章

https://juejin.im/post/5d9ab5dae51d4578453274ba

https://juejin.im/post/5ab116875188255561411b8a#heading-29

想要获取更多精选技术文章推荐的小伙伴们,请长按下图,关注微信公众号前后端精选,或者直接在微信上搜索“前后端精选”即可关注!

image-20200614102027868

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值