多线程的应用场景以及其实现方式

来源:互联网 发布:tim 钉钉 知乎 编辑:程序博客网 时间:2024/05/18 03:29

一、线程的概要绍
从员工搬货看多线程
现在有一大推货物堆着,如果我有5个员工搬,肯定会比一个员工搬要快速。但是若是有15个员工搬同一堆货物,中间肯定会因为空间以及货物争抢而产生摩擦,甚至会互相掐架。所以,这就不意味着线程越多越好,合理的使用多线程,可以充分提升处理器的利用率,提高工作效率

线程与进程
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
所以线程被包含在进程里,两者范畴不一样

多线程使用的目的:
1、 吞吐量:做WEB,容器帮你做了多线程,但是它只能帮你做请求层面的,简单的说,就是一个请求一个线程(如struts2,是多线程的,每个客户端请求创建一个实例,保证线程安全),或多个请求一个线程,如果是单线程,那只能是处理一个用户的请求
2、 伸缩性:通过增加CPU核数来提升性能。
多线程的使用场景:
1、 常见的B/S服务(浏览器、Web服务)web处理请求(一般web中间件会自动管理线程),各种专用服务器(如游戏服务器)
2、 FTP下载,多线程操作文件
3、 数据库用到的多线程
4、 分布式计算
5、 tomcat内部采用多线程,上百个客户端访问同一个WEB应用,tomcat接入后就是把后续的处理扔给一个新的线程来处理,这个新的线程最后调用我们的servlet程序,比如doGet或者dpPost方法
6、 后台任务:如定时向大量(100W以上)的用户发送邮件;定期更新配置文件、任务调度(如quartz),一些监控用于定期信息采集
7、 异步处理:如发微博、记录日志
8、费时应用开发,一个费时的计算开个线程,前台加个进度条显示
9、swing编程

二、多线程的实现式
常用的实现多线程的方有三种,分别是继承Thread类、实现Runnable接口和使用ExecutorService、Callable、Future实现带有返回结果的多线程。下面一一举例说明。

继承Thread类实现多线程
继承Thread类的方法尽管被视为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,但是线程在调用start()方法之后并不是立马就执行,而是将线程处于可执行状态,什么时候执行是由操作系统决定的,在执行期间线程将会执行run()方法里的内容。

package com.test.thread;import java.util.HashMap;import java.util.Map;public class ThreadDemo extends Thread {    private String name;    public ThreadDemo(String name){        this.name = name;    }    @Override    public void run() {            Map<String, String> map = new HashMap<>();            for (int i = 1; i <= 5; i++) {                map.put("name"+i, name);    //将name+1作为key值,value为传入的线程名字                                       }            this.getInfo(map);  //调用map的打印方法    }    public void getInfo(Map<String, String> map){        for (String str : map.keySet()) {                       System.out.println("名为 "+Thread.currentThread().getName()+" value= "+map.get(str));        }    }    public static void main(String[] args) {        Thread t1 = new ThreadDemo("别闹");   //定义一个新线程        t1.start();     //启动线程。注意,线程在启动之后并不一定是立马执行,什么时候执行取决于操作系统        Thread t2 = new ThreadDemo("别闹+1");        t2.start();        Thread t3 = new ThreadDemo("别闹+2");        t3.start();    }}

输出:
名为 Thread-1 value= 别闹+1
名为 Thread-0 value= 别闹
名为 Thread-2 value= 别闹+2
名为 Thread-0 value= 别闹
名为 Thread-1 value= 别闹+1
名为 Thread-0 value= 别闹
名为 Thread-2 value= 别闹+2
名为 Thread-0 value= 别闹
名为 Thread-1 value= 别闹+1
名为 Thread-0 value= 别闹
名为 Thread-2 value= 别闹+2
名为 Thread-1 value= 别闹+1
名为 Thread-2 value= 别闹+2
名为 Thread-1 value= 别闹+1
名为 Thread-2 value= 别闹+2

再运行一下:

名为 Thread-0 value= 别闹
名为 Thread-1 value= 别闹+1
名为 Thread-2 value= 别闹+2
名为 Thread-2 value= 别闹+2
名为 Thread-2 value= 别闹+2
名为 Thread-2 value= 别闹+2
名为 Thread-2 value= 别闹+2
名为 Thread-0 value= 别闹
名为 Thread-0 value= 别闹
名为 Thread-0 value= 别闹
名为 Thread-0 value= 别闹
名为 Thread-1 value= 别闹+1
名为 Thread-1 value= 别闹+1
名为 Thread-1 value= 别闹+1
名为 Thread-1 value= 别闹+1

说明:
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用ThreadDemo 的3个对象的start方法,另外3个线程也启动了,这样,整个应用就在多线程下运行。
由上面的执行结果看出,线程的名字若没有自定义,则会使用默认的名字,格式为“Thread-第几个线程”。线程的执行顺序是随机的,一般正常情况下都是线程间交叉执行。

实现Runnable接口方式实现多线程
我们都知道,一个子类只能继承一个父类,如果一个类继承了一个父类,那就不能通过继承Thread父类来实现多线程了,这时,就可以通过实现Runnable接口,来实现多线程。

package com.test.thread;import java.util.HashMap;import java.util.Map;public class ThreadDemo implements Runnable {    private String name;    public ThreadDemo(String name){        this.name = name;    }    @Override    public void run() {            Map<String, String> map = new HashMap<>();            for (int i = 1; i <= 5; i++) {                map.put("name"+i, name);    //将name+1作为key值,value为传入的线程名字                                       }            this.getInfo(map);  //调用map的打印方法    }    public void getInfo(Map<String, String> map){        for (String str : map.keySet()) {                       System.out.println("名为 "+Thread.currentThread().getName()+" 打印的key= "+str+" value= "+map.get(str));        }    }    public static void main(String[] args) {        Runnable runnable = new ThreadDemo("无厘头");        Thread t1 = new Thread(runnable, "线程1");        t1.start();        Thread t2 = new Thread(runnable,"线程2");        t2.start();        Thread t3 = new Thread(runnable,"线程3");        t3.start();    }}

输出:
名为 线程3 打印的key= name5 value= 无厘头
名为 线程1 打印的key= name5 value= 无厘头
名为 线程1 打印的key= name3 value= 无厘头
名为 线程1 打印的key= name4 value= 无厘头
名为 线程1 打印的key= name1 value= 无厘头
名为 线程1 打印的key= name2 value= 无厘头
名为 线程2 打印的key= name5 value= 无厘头
名为 线程2 打印的key= name3 value= 无厘头
名为 线程2 打印的key= name4 value= 无厘头
名为 线程3 打印的key= name3 value= 无厘头
名为 线程2 打印的key= name1 value= 无厘头
名为 线程2 打印的key= name2 value= 无厘头
名为 线程3 打印的key= name4 value= 无厘头
名为 线程3 打印的key= name1 value= 无厘头
名为 线程3 打印的key= name2 value= 无厘头

说明:
ThreadDemo类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target, String name) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

使用ExecutorService、Callable、Future实现带有返回结果的多线程
上面所讲述的两种实现多线程的方法,都是没有返回值的,如果想要实现带返回值的多线程,不妨试一下,下面这种方法。

@SuppressWarnings("unchecked")  public class Test {  public static void main(String[] args) throws ExecutionException,      InterruptedException {     System.out.println("----程序开始运行----");     Date date1 = new Date();     int taskSize = 5;     // 创建一个线程池     ExecutorService pool = Executors.newFixedThreadPool(taskSize);     // 创建多个有返回值的任务     List<Future> list = new ArrayList<Future>();     for (int i = 0; i < taskSize; i++) {      Callable c = new MyCallable(i + " ");      // 执行任务并获取Future对象      Future f = pool.submit(c);      // System.out.println(">>>" + f.get().toString());      list.add(f);     }     // 关闭线程池     pool.shutdown();     // 获取所有并发任务的运行结果     for (Future f : list) {      // 从Future对象上获取任务的返回值,并输出到控制台      System.out.println(">>>" + f.get().toString());     }     Date date2 = new Date();     System.out.println("----程序结束运行----,程序运行时间【"       + (date2.getTime() - date1.getTime()) + "毫秒】");  }  }  class MyCallable implements Callable<Object> {  private String taskNum;  MyCallable(String taskNum) {     this.taskNum = taskNum;  }  public Object call() throws Exception {     System.out.println(">>>" + taskNum + "任务启动");     Date dateTmp1 = new Date();     Thread.sleep(1000);     Date dateTmp2 = new Date();     long time = dateTmp2.getTime() - dateTmp1.getTime();     System.out.println(">>>" + taskNum + "任务终止");     return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";  }  

输出:

说明:
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
ExecutorService、Callable、Future这些对象实际上都是属于Executor框架中的功能类,推荐大家去看看Executor框架。

0 0