您的位置:首页 > 编程语言 > Java开发

关于java中的线程和线程池的一点总结

2014-06-20 05:30 381 查看
1. 在java中我们如果想要监视应用程序对于计算机资源的使用情况,我们可以调用jdk自带的工具jconsole。具体使用方法:首先运行java程序,并保证java程序在jconsole程序运行之前一直处于运行状态(所以这个程序一般用于监视那些运行时间比较长的java程序,因为这样的程序对于计算机资源的使用情况才具有参考意义);然后在命令行中间输入jconsole指令启动监视程序(如果没有准确地配置环境变量,还可以在jdk所在的文件夹下的bin文件夹中找到jconsole.exe。启动它),这个时候就会看见如下的图形界面:



如果我们有java程序在运行的话,在Local Processes下面就会有运行的java程序显示出来,双击这个程序就可以进入到监视界面了。在监视界面中我们可以监视程序对于cpu,内存这些资源的使用情况,同时我们还可以看到程序中线程的情况(这点对于调试多线程的程序而言是很有效的)。

2. 在java中我们难道只有两种创建线程的方法吗?几乎所有的教科书都告诉我们在java中我们只有两种创建线程的方法(通过Thread类或者Runnable接口,其实这两种方法严格上来说就是一种,都是通过Runnable接口来指定线程的运行任务,从而来创建线程的,因为Thread类也是实现了Runnable接口的,这个类的存在只是为了简化我们创建线程的方法而已)。但是我的一个朋友曾经面试一个公司的时候,被问到了第三种创建线程的方法(当然他是知道的,所以回答上来了)。什么?是的,你没有听错,java中确实是有第三种创建线程的方法——利用泛型接口Callable<E>。谈到这个接口我们就不得不谈到java中的线程池,就像我们谈到Runnable就不得不谈到Thread一样,而且他们的关系也是类似的:对于Callable<E>接口和java线程池而言,Callable<E>负责指定线程的执行任务,而线程池负责创建线程来执行这些任务,并且返回结果(这点就和Runnable接口不同的地方,因为Runnable接口的返回值是void,所以利用它创建的线程是没法返回一个值的。具体的讲解会在后面给出);对于Runnable接口和Thread类而言,Runnable接口负责制定线程的执行任务,而Thread类负责创建和执行线程(一般是通过调用Thread类的start())。最后,我要指出的一点就是Thread类由于值实现了Runnable接口,所以他只能和Runnable接口配合使用,而线程池是java中比较强大的机制,所以它既可以和Callable<E>接口配合使用也可以喝Runnable接口配合使用。这个听起来是不是很不公平?其实没有什么不公平的,看java的官方说明文档我们可以发现,Runnable和Thread自从JDK1.0(1996年发布)一来就存在了,而Callable<E>和java线程池是在JDK1.5(2004年发布)以后才引入的,他们前后相差了将近10年的时间,为了兼容性,我们只能放弃这个“公平”了(这个纯属我个人意见,欢迎吐槽)。

3. Callable<E>和Runnable接口的异同

接口名称

相同点

不同点

Runnable

都是为java创建的线程指定执行的代码(在官方的文档中被称为task)

接口中声明的方法是

void run();

线程执行完task之后是不能有返回值的(如果需要返回值,只能通过类中的field来实现了,这也是Runnable的弊端,也是在jdk1.5中引入Callable<E>的原因)

Callable<E>

接口中声明的方法是:

E call() throws Exception

这个方法是有返回值的,而且是泛型的,说明线程执行完之后是可以有返回值的。当然这个方法还抛出了一个异常,表示如果我们不能计算出返回值,就抛出一个异常。

PS:通过上的分类,我更愿意将java中线程的创建就归为两种(创建一个带返回值的线程(Callable<E>接口)和创建一个不带返回值的线程(Runnable接口))

4. Callable<E>的创建和使用。Callable<E>的创建可以通过以下三种方式:

(1)匿名内部类

(2)一个类实现Callable<E>然后再通过new关键字创建这个类的对象

(3)通过Executors类的静态工厂方法(static factory method)创建一个Callable<E>的实例(这个也是推荐的方法),这个方法里面有大量的称为callable的重载方法,这些方法都是静态方法,都可以用来创建对应的Callable<E>实例,其中有个方法是:

public static <T> Callable<T> callable(Runnable task, T result)

这个方法可以将Runnable转换成Callable<E>。这个方法很有用。最后,需要指出的是,Executors这个类在java线程池的创建中占有很重要的地位,所以我们要牢记这个类。

5. java中的线程池(在java中用,线程池的顶级接口是Executor,一般我们使用的是它的次级接口ExecutorService,当然针对不同类型的线程池有不同的实现类,比如:ThreadPoolExecutor,但是ExecutorService这个接口是我们常用的):

Java中主要有五种方式创建三大类线程池(其实严格来说其中的两种不能称为“池”,而且java中命名也没有将他们称为“pool”,因为他们里面只有一个线程在执行任务,但是为了方便,我也将他们归为“线程池”了):

类别

说明

方法(以下的方法都是Executors这个类的静态方法,所以可以通过这个类直接调用)

使用(通过调用下面的方法可以将线程放入到对应的线程池中,线程池会在合适的时候执行该线程)

单线程的线程池

严格意义上来说并不算是“池”,因为它只有一个线程,而且java中的命名也没有称他是“pool”,由于它的创建方法和后面两种类似,所以归为这类。

ExecutorService newSingleThreadExecutor();

ScheduledExecutorService newSingleThreadScheduledExecutor();

ExecutorService类继承自Executor的execute方法或者自己的submit方法,前者只能接收Runnable对象,无返回值,后者可以接收Callable<E>和Runnable对象,且有返回值。

ScheduledExecutorService自己的一些带有schedule字样的方法

固定大小的线程池

在线程池中只允许同时执行固定数量的线程。推荐使用,因为它不会将计算机的资源用尽,但是对于那些很快就能执行完的任务,推荐用下面的线程池。

ExecutorService newFixedThreadPool(int nThreads);

ScheduledExecutorService newScheduledThreadPool(int corePoolSize);

无限大小的线程池

在线程池中运行同时执行无限数量(从一定程度上来说可以是无限)的线程。对于执行那些很快就能执行完的小任务推荐使用。

ExecutorService newCachedThreadPool();

PS:我最后简单说明一下“线程池”这个概念:线程池是为了更好的利用计算机资源,避免不必要的线程创建,更好的管理线程而引入的一种机制,在线程池中主要有两种方法borrow和return,borrow方法负责从线程池中取出现有的线程(如果没有,才创建新的线程)来执行“到来的任务”(比如HTTP请求),这样就避免了线程的创建(线程的创建是很麻烦的一个过程),提高了性能。return方法负责将执行完任务的线程归还给线程池,以便重复利用这个线程。所以我们可以简单的将线程池理解为一个十分牛逼的线程管理器。最后,普及一下操作系统的知识:java中的线程的创建和实现其实是依靠底层的操作系统的,所以不同的平台上,java的线程的创建和实现不一定相同,但是有一点是肯定的,JVM可以很好的根据计算机的CPU的个数(有的称为核心(core)的个数)和线程来创建线程,使它运行在不同的CPU上,实现真正的并行运行。

上面红色标出的“线程”和我们前面提到的线程是不同的,前面的线程是对于进程(process)而言的,而这里的线程是对于计算机体系结构中的流水线(pipeline)而言的。红色字标出的线程是一个称为超线程的技术,表示可以将一个CPU当成多个,一般是两个。比如我们听到的双核四线程的机子,虽然在逻辑上我们可以看成是四核的机子,但是性能并没有真正的四核的机子好,但是他比四核的机子省电,所以在苹果的笔记本电脑(macbook)上主要是用超线程的技术来模拟多核,而在其他的机子上则会采用真正的多个CPU来实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: