Java线程和线程池
Posted MalcolmF
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程和线程池相关的知识,希望对你有一定的参考价值。
一、Java线程
几个概念:
进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
线程: 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
多线程:在一个进程中创建多个线程,用多线程只有一个目的,那就是更好的利用cpu的资源,如磁盘IO,网络,计算等。
并行与并发:
-
- 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时,如Hadoop中MapReduce的并行计算等;
- 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时,而是cpo对于多个线程进行cpu资源的调度;
线程的创建方式:
new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start();
线程的状态:
线程在Running的过程中可能会遇到阻塞(Blocked)情况
- 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
- 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
- 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。
此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。
线程安全:多线程在并发情况下对于共用的属性进行操作时,会出现线程不安全现象,因此,我们需要对操作公共属性的代码或者方法进行同步锁,以保证属性的线程安全。通常,我们使用synchronized关键字来实现同步锁:
synchronized对于代码块的使用:
public class Thread1 implements Runnable { Object lock; public void run() { synchronized(lock){ ..do something } } }
synchronized对于方法的使用:
public class Thread1 implements Runnable { public synchronized void run() { ..do something } }
使用wait和notify来实现:
/** * 生产者生产出来的产品交给店员 */ public synchronized void produce() { if(this.product >= MAX_PRODUCT) { try { wait(); System.out.println("产品已满,请稍候再生产"); } catch(InterruptedException e) { e.printStackTrace(); } return; } this.product++; System.out.println("生产者生产第" + this.product + "个产品."); notifyAll(); //通知等待区的消费者可以取出产品了 } /** * 消费者从店员取产品 */ public synchronized void consume() { if(this.product <= MIN_PRODUCT) { try { wait(); System.out.println("缺货,稍候再取"); } catch (InterruptedException e) { e.printStackTrace(); } return; } System.out.println("消费者取走了第" + this.product + "个产品."); this.product--; notifyAll(); //通知等待去的生产者可以生产产品了 }
如果创建多线程应用,每次都new Thread对象的话,会有很多的弊端,如:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
因此,java提供线程池的方式,用于创建线程任务,并对线程任务进行管理,如获取执行状态,销毁线程等等。
线程管理类:
Executor(顶级接口):
并发编程的一种编程方式是把任务拆分为一系列的小任务,即Runnable,然后将这些任务提交给一个Executor执行,Executor.execute(Runnalbe) 。Executor在执行时使用其内部的线程池来完成操作。execute没有返回值。
Executor的子接口有:
ExecutorService、ScheduledExecutorService
Executor的已知实现类:
AbstractExecutorService、ScheduledThreadPoolExecutor、ThreadPoolExecutor。
ExecutorService(接口):
ExecutorService接口继承了Executor接口,是Executor的扩展子接口;
ExecutorService中的submit接收的实现Runable接口的对象或者callable接口的对象;
Executor中的execute方法没有返回值,而submit方法有future返回值;
future的使用:
1.如果任务已经执行完成,就可以通过 Future.get() 方法获得执行结果。需要注意的是,Future.get() 方法是一个阻塞式的方法,如果调用时任务还没有完成,会等待直到任务执行结束。
2.可以通过Future.cancel()取消pending中的任务;
3.当调用 shutDown 方法时,线程池会停止接受新的任务,但会完成正在 pending 中的任务;
Executors(类):
Executors 是一个工具类,类似于 Collections。提供工厂方法来创建不同类型的线程池,如:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
CompletionService(类):
使用ExecutorService类的时候,我们常维护一个list保存submit的callable task所返回的Future对象。然后在主线程中遍历这个list并调用Future的get()方法取到Task的返回值。
其实除了使用ExecutorService外,还可通过CompletionService包装ExecutorService,然后调用其take()方法去取Future对象。
区别:CompletionService和ExecutorService的主要的区别在于submit的task不一定是按照加入自己维护的list顺序完成的。
ExecutorService中从list中遍历的每个Future对象并不一定处于完成状态,这时调用get()方法就会被阻塞住,如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于list后面的但是先完成的线程就会增加了额外的等待时间。
而CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。所以,先完成的必定先被取出。这样就减少了不必要的等待时间。
总结:异步计算的线程按照职责分为3类:
1. 异步计算的发起线程(控制线程):负责异步计算任务的分解和发起,把分解好的任务交给异步计算的work线程去执行,发起异步计算后,发起线程可以获得Futrue的集合,从而可以跟踪异步计算结果。
2. 异步计算work线程:负责具体的计算任务
3. 异步计算结果收集线程:从发起线程那里获得Future的集合,并负责监控Future的状态,根据Future的状态来处理异步计算的结果。
参考文章:
Java中的多线程你只要看这一篇就够了
Java并发编程 - Executor,Executors,ExecutorService, CompletionServie,Future,Callable
以上是关于Java线程和线程池的主要内容,如果未能解决你的问题,请参考以下文章
newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段