java中创建线程的4种方式

写在前面的话

java线程创建方式有几种?这种问题在面试中经常被问到,你可能心里马上反映出两种方式(实现Runnable、继承Thread),当你把这两种叙述给面试官听后,面试官会觉得你该掌握的知识已经有了,但是仅仅而已。如果你还说了callable与future、线程池,那么面试官就会认为你 知识覆盖面广,对你好感加一!下面首先叙述为什么会出现线程,以及线程在实际生活中的例子,紧接着给出四种创建线程的方式,加以代码进行演示。喜欢博主文章的你,动动你们的小爪子,点赞收藏呗(#^.^#)

1、线程的知识

1.1、什么是多线程

百度百科这样说:多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。好像和没说一样,概念还是不怎么理解。那就举个例子:早上小明起床后,需要完成刷牙、洗脸、烧水、听广播,为了节约时间、提高效率,小明边烧水、边听广播、边刷牙洗脸,这三个任务并行进行,就类似于三个线程。多线程就是同时多个线程同时工作,并发执行任务。

1.2、为什么会有多线程

这个问题可以转换为:多线程带来了什么好处与优势。结合《JAVA并发编程实践》总结如下:

1、发挥多处理器的强大能力

现在的处理器在频率上的提升空间越来越小,为了增加设备整体的性能,出现了多核多处理器设备。由于线程是cpu调度的基本单位, 当一个程序中只有一个线程时,最多只能在一个CPU上进行运行,其他CPU将处于空闲状态,严重影响多核CPU的性能。因此,如果涉及合理正确的多线程程序,将会充分利用多核CPU性能优势,提高整体设备性能。

2、建模的简单性

通过使用线程,可以将复杂并且异步的工作流进一步分解为一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置进行交互。我们可以通过一些现有框架来实现上述目标,例如Servlet和RMI,框架负责解决一些细节问题,例如请求管理、线程创建、负载平衡,并在正确的时候将请求分发给正确的应用程序组件。编写Servlet的开发人员不需要了解多少请求在同一时刻要被处理,也不需要了解套接字的输入流或输出流是否被阻塞,当调用Servlet的service方法来响应Web请求时,可以以同步的方式来处理这个请求,就好像它是一个单线程程序。

3、异步事件的简化处理

服务器应用程序在接受多个来自远程客户端的套接字连接请求时,如果为每个连接都分配其各自的线程并且使用同步I/O,那么就会降低这类程序的开发难度。如果某个应用程序对套接字执行读操作而此时还没有数据到来,那么这个读操作将一直阻塞,直到有数据到达。在单线程应用程序中,这不仅意味着在处理请求的过程中将停顿,而且还意味着在这个线程被阻塞期间,对所有请求的处理都将停顿。为了避免这个问题,单线程服务器应用程序必须使用非阻塞I/O,但是这种I/O的复杂性要远远高于同步I/O,并且很容易出错。然而,如果每个请求都拥有自己的处理线程,那么在处理某个请求时发生的阻塞将不会影响其他请求的处理。

4、响应更灵敏的用户界面

GUI界面采用多线程,可以同时响应多个按钮,比如在IDEA中,在程序运行时,还可以继续进行程序编写。

1.3、线程与进程的区别

进程是操作系统进行资源分配的单元,线程是CPU调度运行的单位;一个进程中可以包含很多线程,线程共享进程的内存等资源;每个进程拥有各自独立的一套变量,相互不影响,而线程则共享数据,会存在线程安全问题。

1.4、举例说明线程与进程

为了能够看到本地计算机上的线程信息,下载Process Explorer,解压运行进行查看。

 从上图可以形象的验证,进程与 线程之间的关系。关于Process Explorer的用法,感兴趣的可以自行研究。

2、java创建线程的四种方式

2.1、实现Runnable

通过实现Runnable接口,重写run()方法。然后借助Thread的start()方法开启线程,调用run()方法是不会开启新线程的,只是一次方法调用而已。

看一下Runnable接口的java代码,发现它只有一个run()方法。

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

进行线程编写, 

public class CreateThreads implements Runnable{

    @Override
    public void run() {
        System.out.println("通过实现Runnable接口开启线程~");
    }

    public static void main(String[] args) {
        CreateThreads createThreads = new CreateThreads();
        Thread thread = new Thread(createThreads);
        thread.start();
    }
}

2.2、继承Thread

继承Thread类,重写run()方法。

public class CreateThreads extends Thread{

    @Override
    public void run() {
        System.out.println("通过继承Thread开启线程~");
    }

    public static void main(String[] args) {
        CreateThreads createThreads = new CreateThreads();
        Thread thread = new Thread(createThreads);
        thread.start();
    }
}

注意:继承Thread类,重写run()方法,其本质上与实现Runnable接口的方式一致,因为Thread类本身就实现了Runnable接口

public class Thread implements Runnable 。再加上java中多实现,单继承的特点,在选用上述两种方式创建线程时,应该首先考虑第一种(通过实现Runnable接口的方式)。

2.3、通过Callable、Future

通过Runnable与Thread的方式创建的线程,是没有返回值的。然而在有些情况下,往往需要其它线程计算得到的结果供给另外线程使用( 例如:计算1+100的值,开启三个线程,一个主线程,两个计算线程,主线程需要获取两个计算线程的结算结果(一个计算线程计算1+2+...+50,另外一个线程计算51+52+..+100),进行相加,从而得到累加结果),这个时候可以采用Runnable与Thread的方式创建的线程,并通过自行编写代码实现结果返回,但是不可避免的会出现黑多错误和性能上的问题。基于此,JUC(java.util.concurrent)提供了解决方案,实现Callable的call()方法(这个类似Runnable接口),使用Future的get()方法进行获取。

下面首先看一下Callable接口:

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

下面开始使用Callable、与Future创建多线程。创建过程为:1、自定义一个类实现Callable接口,重写call()方法;2、使用JUC包下的ExecutorService生成一个对象,主要使用submit()方法,返回得到Future对象(关于JUC包下的诸如ExecutorService解析使用,请关注博主的后序文章);3、采用Future的get()获取返回值。

import java.util.concurrent.*;

/**
 * @author wolf
 * @create 2019-04-25    14:53
 * 计算1+...+100的结果,开启三个线程,主线程获取两个子线程计算的结果,一个子线程计算1+...+50,一个子线程计算51+...+100。
 */
public class CreateThreads implements Callable {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //生成具有两个线程的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //调用executorService.submit()方法获取Future
        Future<Integer> result1 = executorService.submit(new CreateThreads());
        Future<Integer> result2 = executorService.submit(new SubThread());
        //使用Future的get()方法等待子线程计算完成返回的结果
        int result = result1.get() + result2.get();
        //关闭线程池
        executorService.shutdown();
        //打印结果
        System.out.println(result);

    }

    //子线程1,用来计算1+...+50
    @Override
    public Object call() throws Exception {
        int count = 0;
        for (int i = 1; i <= 50; i++)
            count = count + i;
        return count;
    }
}

class SubThread implements Callable {
    //子线程2,用来计算51+...+100
    @Override
    public Object call() throws Exception {
        int count = 0;
        for (int i = 51; i <= 100; i++)
            count = count + i;
        return count;
    }
}

2.4、通过JUC里面的线程池

JUC并发包里的 Executors工具类,提供了创建线程池的工厂方法,具体可以参看JDK的API。

下面就Executors.newFixedThreadPool()说明,这里请移步2.3节的程序,会发现其实2.3节已经使用了Executors.newFixedThreadPool(2)构建线程池。

 

3、总结

通过上述叙述,希望对你知识掌握和面试有所帮助。多线程是java中的进阶,这一块还有很多知识点,本文后会继续发布关于多线程博客,旨在介绍java中多线程的框架,介绍JUC包,一起学习共勉。文中有错误的地方,还请留言给予指正,不胜感激~最后,喜欢博主的文章不妨关注点赞收藏哦,方便下次不走错门。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值