天天看点

java中多线程详解(一)

搞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()方法,会阻塞直到计算完成。

在详解二中会分享线程状态切换和线程状态

继续阅读