多线程,线程的创建和启动,Thread、Runnable、Callable的用法


前言

大部分时候我们都作者单线程编程,程序都只有一条顺序执行流:从main方法开始执行,一次向下执行每行代码。

但实际情况是,单线程的程序往往功能非常有限,例如我们要开发一个煎蛋的服务器程序,这个服务器程序需要向不同的客户端提供服务,不同客户端之间互不干扰,这就需要多线程。

多线程程序可以包括多个顺序执行流,多个顺序流之间互不干扰。Java提供了非常优秀的多线程支持,程序可以通过非常简单的方式来启动多线程。


一、线程和进程的定义

进程就是一个应用程序(一个进程是一个软件)
线程是一个进程中的执行场景/执行单元
一个进程可以启动多个线程

对于java程序来说,当在DOS命令窗口中输入:
java HelloWorld 回车之后,会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾。
最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。

举个例子:

	阿里巴巴:进程
		马云:阿里巴巴的一个线程
		童文红:阿里巴巴的一个线程
	京东:进程
		强东:京东的一个线程
		奶茶:京东的一个线程
	
	进程可以看做是现实生活当中的公司。
	线程可以看做是公司当中的某个员工。

二、进程和线程的关系

进程A和进程B之间的内存独立不共享,就像阿里巴巴和京东是两个独立的公司,资源不会共享。QQ一个进程,微信是一个进程,这两个进程是独立的,不共享资源。

但是线程不一样。

同一个进程中的线程A和线程B堆内存和方法区内存共享,栈内存独立,一个线程一个栈。
假设启动10个线程,就会有10个栈空间,每个栈之间互不干扰,各自执行各自的,这就是多线程开发。

多线程的例子:

		火车站,可以看做是一个进程。
		火车站中的每一个售票窗口可以看做是一个线程。
		我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。
		所以多线程并发可以提高效率。
		java中之所以有多线程机制,目的就是为了提高程序的处理效率。

三、线程的创建和启动

Java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。

3.1 继承java.lang.Thread

通过继承Thread类来创建并启动多线程步骤:
(1)定义一个类继承Thread,并重写run()方法,该run()方法的方法体代表了该线程需要完成的任务。把run()方法称为线程执行体;
(2)创建Thread子类MyThread的实例,即创建了线程对象;
(3)调用线程对象的start()方法来启动该线程。

public class MyThread  extends Thread {
    private int i;
    @Override
    public void run() {
        for ( ; i < 100; i++) {
            System.out.println(getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < i; i++) {
         //调用Thread的currentThread().getName()方法获取当前线程
            System.out.println(Thread.currentThread().getName() + " " + i);
        }

        MyThread myThread1 = new MyThread();
        myThread1.start();
        MyThread myThread2 = new MyThread();
        myThread2.start();
    }
}

运行结果:
在这里插入图片描述

3.2 实现java.lang.Runnable接口

通过实现Runnable接口来创建并启动多线程步骤:
(1)定义一个类实现Runnable接口,并重写run()方法,该run()方法的方法体代表了该线程需要完成的任务。把run()方法称为线程执行体;
(2)创建Runnable实现类MyRunnable 的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象;
(3)调用线程对象的start()方法来启动该线程。

public class MyRunnable implements Runnable {
    private int i;
    @Override
    public void run() {
        for ( ; i < 100; i++) {
            //与继承Thread类不同的事,这里不能用getName()方法来获取当前线程
            //System.out.println(getName() + " " + i);
            //只能用Thread.currentThread().getName()来获取当前线程
            System.out.print(Thread.currentThread().getName() + " " + i +";");
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < i; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        MyRunnable myRunnable = new MyRunnable();
        //此处创建线程的时候取了名字
        new Thread(myRunnable,"A线程").start();
        new Thread(myRunnable,"B线程").start();
    }
}

运行结果:
在这里插入图片描述

3.3 实现java.util.concurrent.Callable接口

从Java5开始,Java提供了Callable接口,该接口就像是Runnable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。

利用Callable创建并启动线程步骤:
(1)创建Callable接口的实现类,并实现call()方法,该call()方法作为线程的执行体,且该call()方法有返回值;
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程;
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int i = 0;
        for ( ; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "的循环变量i的值:"+i );
        }
        return i;
    }

    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(myCallable);
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()  + "的循环变量i的值:"+i);
            new Thread(task).start();
        }
        try {
            System.out.println("子线程的返回值:"+task.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:
在这里插入图片描述

四、Runnable和Callable对比

4.1 相同点:

(1)都是接口;
(2)都可以编写多线程程序;
(3)都采用Thread.start()启动线程。

4.2 不同点:

(1)Runnable没有返回值;Callable可以返回执行结果;
(2)Callable接口的call()允许抛出异常;Runnable的run()不能抛出。

二者实现方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已,因此将二者归为一种方式。

五、Runnable和Callable与Thread比较

5.1 使用Runnable和Callable的优势

(1)线程类只是实现了接口,还可以继承其他类;
(2)多个线程可以共享一个target,非常适合多个相同的线程处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现面向对象的思想;
(3)线程池技术,接受Runnable,不接受Thread。

5.2 使用Runnable和Callable的劣势

(1)编程稍稍复杂,如果需要访问当前程序,必须使用Thread.currentThread()方法;

5.3 使用Thread的劣势

(1)线程继承了Thread类,不能继承其他类;

鉴于上面的分析,一般推荐采用实现Runnable和Callable接口方式来创建多线程。


拓展思考

思考题1:使用多线程后,main方法结束,是不是程序就结束了?

	不是。main方法结束只是主线程结束了,主栈空了,但是其它的栈(线程)可能还在压栈弹栈。

思考题2:对于单核的CPU来说,真的可以做到真正的多线程并发吗?

	对于多核的CPU电脑来说,真正的多线程并发是没问题的。
		4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
	什么是真正的多线程并发?
		t1线程执行t1的。
		t2线程执行t2的。
		t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。

	单核的CPU表示只有一个大脑:
		不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
		对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于
		CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是:多个事情
		同时在做!!!!!
			线程A:播放音乐
			线程B:运行魔兽游戏
			线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,
			给我们的感觉是同时并发的。
	
	电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,
	人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像
	一根钢针扎到手上,到最终感觉到疼,这个过程是需要“很长的”时间的,在
	这个期间计算机可以进行亿万次的循环。所以计算机的执行速度很快。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值