搞IT行业,遇到不懂的技术,在学习的时候,一定要先搞明白这是什么,用在什么地方,怎么用
线程这一块,对我来说,还是比较陌生,借这一次总结,好好地梳理一下这一块,另外,也希望能帮助到和我有一样困惑的朋友们,相互学习!
一、简单介绍一下多线程的概念、使用目的以及使用场景
1、首先讲讲线程和进程的区别
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小
进程和线程一样分为五个阶段:创建、就绪、运行、阻塞、终止
多进程是指操作系统能同时运行多个任务(程序)
多线程是指在同一个程序中有多个顺序流在执行
2、多线程使用目的
吞吐量:JavaWeb开发时,web容器已经做了请求层面的多线程,但仅仅只限于请求层面,简单讲就是一个请求一个线程,如struts2是多线程的,每个客户端请求创建一个实例,保证线程安全,或者多个请求一个线程,如果是单线程,同一时间只能处理一个用户的请求,这样显然是不允许的
伸缩性:通过增加CPU核数来提升性能
3、使用场景
1)常见的浏览器、web服务(现在的web大部分是中间件完成了线程的控制),web处理请求、各种专用服务器(如游戏服务器)
2)servle多线程
3)FTP下载,多线程操作文件,如百度云盘下载
4)java多线程操作数据库,如数据库百万条的数据分、数据迁移
5)分布式计算
6)tomcat内部采用多线程,上百个客户端访问同一个web应用,tomcat接入后就是把后续的处理扔给一个新的线程来处理,这个新的线程最后调用我们的servlet程序,比如doGet或者doPost方法
7)后台任务,如定时向大量的用户发送邮件、定期更新配置文件、任务调度(如quartz),一些用于定时采集的监控等
8)自动作业处理,如定期备份日志、定期备份数据库
9)异步处理,如发微博、记录日志
10)页面异步处理,如大批量数据的核对处理(如十万个用户名,核对那些是被占用的)
二、java中多线程的实现方法
java中实现多线程的有两种方法,一、继承Thread类,二、实现Runnable接口,三、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。
1、继承Thread类
public class ExtendsThread {
public static void main(String[] args){
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
public static class MyThread extends Thread{
//车票数量
private int tickets=100;
@Override
public void run() {
while(tickets>0){
System.out.println(this.getName()+"卖出第【"+tickets--+"】张火车票");
}
}
}
}
此程序用来模拟4个售票窗口同时售卖100张票,但是数据不是共享的,所以会四个线程分别售出100张票
2、实现Runnable接口
public class ImplRunnable {
public static class Ticket implements Runnable{
private int tickets = 100;
@Override
public void run() {
while(tickets>0){
System.out.println(Thread.currentThread().getName()+"卖出第【"+tickets--+"】张火车票");
}
}
}
public static void main(String[] args) {
Runnable ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
此程序用实现Runnable接口来实现四个售票窗口同时售卖100张票,这里的数据是共享的,四个窗口共同来出售这100张票
在实际工作中,几乎所有的多线程都是通过实现Runnable接口来实现的
Runnable适合多个相同程序代码的线程去处理同一资源的情况。把虚拟CPU(线程)同程序的代码、数据有效的分离,较好的体现了面向对象的设计思想。避免由于Java的单继承特性带来的局限性。也就是如果新建的类要继承其他类的话,因为JAVA中不支持多继承,就只能实现java.lang.Runnable接口。有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。继承Thread类不能再继承他类了。编写简单,可以直接操纵线程,无需使用Thread.currentThread()
简单的来说,就是继承和实现接口的区别。
1)、当使用继承的时候,主要是为了不必重新开发,并且在不必了解实现细节的情况下拥有了父类我所需要的特征。它也有一个很大的缺点,
那就是如果我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承 Thread 类,
2)、java只能单继承,因此如果是采用继承Thread的方法,那么在以后进行代码重构的时候可能会遇到问题,因为你无法继承别的类了,在其他的方面,
两者之间并没什么太大的区别。
3)、implement Runnable是面向接口,扩展性等方面比extends Thread好。
4)、使用 Runnable 接口来实现多线程使得我们能够在一个类中包容所有的代码,有利于封装,它的缺点在于,我们只能使用一套代码,若想创建多个
线程并使各个线程执行不同的代码,则仍必须额外创建类,如果这样的话,在大多数情况下也许还不如直接用多个类分别继承 Thread 来得紧凑。
3、使用ExecutorService、Callable、Future实现有返回结果的多线程
ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类。想要详细了解Executor框架的可以访问http://www.javaeye.com/topic/366591 ,这里面对该框架做了很详细的解释。返回结果的线程是在JDK1.5中引入的新特征,确实很实用,有了这种特征我就不需要再为了得到返回值而大费周折了,而且即便实现了也可能漏洞百出。可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:
package client.demo.bean;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableTest {
@SuppressWarnings("rawtypes")
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对象
@SuppressWarnings("unchecked")
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 + "毫秒】";
}
}
代码说明:
上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
在详解二中会分享线程状态切换和线程状态