Java多线程与并发(1):线程基础和Java线程的创建

进程和线程

定义:

进程:是具有一定独立功能的程序关于某个数据集合上的一次运行活动,他是系统进行资源分配和调度的一个独立单位,一个应用程序包含一个或多个进程。

 

线程:是CPU调度和分派的基本单元,是进程中的一个实体,一个进程包含至少一个线程,

 

关系:一个进程至少包含一个线程,同一个进程中的多个线程可以共享进程的所分配的资源,在应用程序运行的时候,实际是每个线程在执行,比进程更小的能独立运行的基本单位,但是离不开线程

 

区别:

  • 进程有独立的地址空间,一个进程崩溃后,不会对其他进程产生影响
  • 线程没有独立的地址空间,线程只有自己的堆栈和局部变量,他们共享所在进程的资源

 

 

分析:

一个线程死掉的话整个进程就会死掉,所以多进程的程序要比多线程的健壮,那么什么还要多线程呢?

原因之一:多进程之间的切换时,耗费资源大,效率也低,而线程之间的切换时,耗费资源相对要小的多,效率也高,而且创建

一个进程和销毁一个进程的消耗也比线程要大的多。

原因之二:有些实际情况只能用多线程完成,就比如一些要求同时进行并且要共享某些变量的并发操作的时候,只能用线程

 

说了线程和进程的基础知识后,来看看下面的内容

 

线程的状态:

这里推荐一篇文章,该博主还有很多好的文章大家可以好好阅读学习:http://www.cnblogs.com/waterystone/p/4920007.html 

 

多线程

什么是多线程:(这里不知道怎么用专业的定义来说,就按照自己的理解去表述吧)多线程就是多个线程同时存在,指一个应用程序的进程运行时产生了不只一个线程

 

多线程的作用:多线程的作用就是为了更好的利用CPU的资源,提高程序的响应时间,当然还有一些必须的场景下需要使用多线程,就比如高并发的情况下,如果使用单线程去处理高并发请求的话,已经不适合这个时代的。

 

说了多线程是什么,那么重要的是在java中如何实现多线程,以及java当中实现多线程的整个架构

 

JAVA多线程的实现

Java实现的多线程的三种方式:

继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现,第三种实现有返回值的多线程,前面两种的实现在线程执行完成之后都没有返回值。

(这里简单的方式就贴简单的代码了)

(1)继承Thread类的实现

Thread本质上也是实现了Runnable接口的一个实例,重写了run方法,启动的话就调用对象的start()方法,执行线程任务就线程实行run()方法

public class MyThread extends Thread {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}  

(2)实现Runnable接口

很容易明白,如果一个类继承了另一个类,就只有实现接口了。

public class MyThread extends OtherClass implements Runnable {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}  

启动方式

MyThread myThread = new MyThread();  
Thread thread = new Thread(myThread);  
thread.start();  

 

(3)线程池

Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。具体的三个实现:(具体代码编译器里面有)

(1)public static ExecutorService newFixedThreadPool(int nThreads)

创建固定数目线程的线程池。

(2)public static ExecutorService newCachedThreadPool()

创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

(3)public static ExecutorService newSingleThreadExecutor()

创建一个单线程化的Executor。

(4)public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

ExecutorService的生命周期:(http://www.iteye.com/topic/366591)

ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行 ,关闭 ,终止。Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。

如果Executor处于关闭状态,往Executor提交任务会抛出unchecked exception RejectedExecutionException。

 

上面讲的是线程池的用法, 现在来详细介绍线程池

 

JAVA线程池

什么是线程池?

百度百科:线程池是一种多线程的处理形式,处理过程中将任务添加到任务队列,然后在创建线程后自动启动这些任务。

自己的理解:其实就是一块内存空间,里面存放了众多的未死亡的线程。

 

线程池作用

先分析存在理由->线程的创建和切换都是代价比较大的,如果在一个高并发的情况下,服务器为了加快客户请求的响应速度,要频繁创建新的线程来响应客户请求,这样的话会造成很大的一部分开销就是在线程的创建和响应结束后的线程的销毁上面,而如果有线程池的话,就可以在线程池中有空闲线程的情况下,直接使用池中的线程,用完之后又把线程归换池中,这样就节省了大量创建线程和销毁线程的开销,减轻服务器的负担。

那么线程池的作用或者说好处就有下面的这些:

  1. 1.降低资源消耗:通过重用线程池还存活的线程来降低线程创建的和销毁的消耗
  2. 2.提高响应速度:任务到达不需要等待线程创建就可以立即执行了(当然是在有空闲的线程的情况下)
  3. 3.提高线程的可管理性:线程可以统一管理、分配、调优和监控

 

Java多线程池的实现支持------ThreadPoolExecutor (java.util.concurrent包下的)

线程池的创建可以通过ThreadPoolExecutor的构造方法实现:(具体详细的源码解析:http://www.jianshu.com/p/ade771d2c9c0

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

具体解释一下上述参数:

  1. corePoolSize 核心线程池大小
  2. maximumPoolSize 线程池最大容量大小
  3. keepAliveTime 线程池空闲时,线程存活的时间
  4. TimeUnit 时间单位
  5. ThreadFactory 线程工厂
  6. BlockingQueue任务队列
  7. RejectedExecutionHandler 线程拒绝策略

 

线程池结构详解

java多线程池支持主要通过ThreadPoolExecutor来实现的,我们使用ExecutorService的各种线程池策略都是基于ThreadPoolExecutor实现的

那么如何去了解ThreadPoolExecutor呢?先看一种图(有个链接http://www.jianshu.com/p/ade771d2c9c0)

图解:(其实这个不是很好理解,看下面一个图或许更好)

step1:调用ThreadPoolExecutor的executor提交线程,首先检查CorePool,如果线程池内的线程小于CorePoolSize,新创建线程执行任务。

step2:如果当前线程池内的线程数大于等于CorePoolSize,那么将线程加入到BlockingQueue.

step3:如果不能加入BlockingQueue,在线程数量小于MaxPoolSize的情况下创建线程执行任务

step4:如果线程数大于MaxPoolSize时,那么执行拒绝策略

 

再看个图(http://blog.csdn.net/lipc_/article/details/52025993 讲的真的好)

图解:(这个图很直观)从图中可以看到构建线程池的几个重要元素

●等待队列:就是调用线程池中的线程对象的submit()方法或者execute()方法,要求线程池运行的任务(这些任务必须实现Runnable或者Callable接口)。但是出于某些原因线程池并没有马上运行这些任务,而是送入一个队列等待执行。

●核心线程数:线程池主要用于执行任务的“核心线程”,就是对应创建线程池的时候CorePoolSize的参数决定的

●非核心线程:一但任务数量过多,也就是核心都在忙着任务,但是还有新的任务进来,那么线程池就会创建“非核心线程”临时帮助运行任务。如果超出的非核心线程在等待keepAliveTime时间后还没有任务可以执行,那么就会将这部分非核心线程给销毁了,使得线程池中的数量保持在CorePoolSize

●MaximumPoolSize参数对应是当前线程池允许创建的最大线程数量,keepAliveTime和timeUnit只在MaximumPoolSize大于CorePoolSize的情况下有意义

●keepAliveTime参数和timeUnit参数也是配合使用的,一个是时间,一个是时间的单位

 

整个线程池接受任务到执行完或者拒绝任务的过程:

说完了线程池的逻辑结构,下面我们讨论一下线程池是怎样处理某一个运行任务的。 

1、首先可以通过线程池提供的submit()方法或者execute()方法,要求线程池执行某个任务。线程池收到这个要求执行的任务后,会有几种处理情况: 

1.1、如果当前线程池中运行的线程数量还没有达到corePoolSize大小时,线程池会创建一个新的线程运行你的任务,无论之前已经创建的线程是否处于空闲状态。 

1.2、如果当前线程池中运行的线程数量已经达到设置的corePoolSize大小,线程池会把你的这个任务加入到等待队列中。直到某一个的线程空闲了,线程池会根据设置的等待队列规则,从队列中取出一个新的任务执行。 

1.3、如果根据队列规则,这个任务无法加入等待队列。这时线程池就会创建一个“非核心线程”直接运行这个任务。注意,如果这种情况下任务执行成功,那么当前线程池中的线程数量一定大于corePoolSize。 

1.4、如果这个任务,无法被“核心线程”直接执行,又无法加入等待队列,又无法创建“非核心线程”直接执行,且你没有为线程池设置RejectedExecutionHandler。这时线程池会抛出RejectedExecutionException异常,即线程池拒绝接受这个任务。(实际上抛出RejectedExecutionException异常的操作,是ThreadPoolExecutor线程池中一个默认的RejectedExecutionHandler实现:AbortPolicy,这在后文会提到) 

2、一旦线程池中某个线程完成了任务的执行,它就会试图到任务等待队列中拿去下一个等待任务(所有的等待任务都实现了BlockingQueue接口,按照接口字面上的理解,这是一个可阻塞的队列接口),它会调用等待队列的poll()方法,并停留在哪里。 

3、当线程池中的线程超过你设置的corePoolSize参数,说明当前线程池中有所谓的“非核心线程”。那么当某个线程处理完任务后,如果等待keepAliveTime时间后仍然没有新的任务分配给它,那么这个线程将会被回收。线程池回收线程时,对所谓的“核心线程”和“非核心线程”是一视同仁的,直到线程池中线程的数量等于你设置的corePoolSize参数时,回收过程才会停止。

 

线程池这里就介绍了前几个参数,还有ThreadFactory 线程工厂,BlockingQueue任务队列,RejectedExecutionHandler 线程拒绝策略没介绍,具体见前面的链接里

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值