多线程JAVA
线程中常见的方法:
Thread.yeild(); 建议线程调度器,我已经执行完了任务的重要部分,此刻正是把cpu切换给其他任务的最佳时期
Thread t;
t.join(); //当前线程等待t线程执行完后在执行当前线程;
t.join(millsecond); //等待多长时间后产生中断异常
线程池:
JAVA5后推荐使用这种,大致分为四种类别的线程池:
1. ExecutorService es = Executors.newCachedThreadPool();
缓存线程池,该线程池都会创建于所需线程数量一样的线程,就停止创建新的线程
2. ExecutorService es = Executors.newFixedThreadPool(15);
固定数量的线程池,当你可预先知道运行所需线程数量,可以使用该线程池,一次性创建完所线程,节省时间;
3. ExecutorService es = Executors.newScheduledThreadPool();
创建一个定长线程池,支持定时及周期性任务执行
4. ExecutorService es = Executors.newSingleThreadExecutor();
拥有一个线程的线程池,多用于长期存活的任务,可以使用该线程;对它提交多个线程任务时,则运行线程必须等上一个线程结束后才能执行下一个线程;它可以很方便的解决同步共享资源,例如,多个线程将要访问同一文件系统,使用singleThreadExecutor就会让多个线程一个一个访问系统,很方便的解决了多线程同步问题
线程池用法:
1. new一个类型的线程池
2. 线程池的submit方法提交线程任务
3. 等待cpu轮询执行任务
4. 使用完毕后要shutdown关闭线程池
关闭线程池有两种方法
shutdown和shutdownnow,前者调用后,会等待当前任务线程并且等候对列里面的线程都执行完后才关闭;而shutdownnow则会任务线程和等待队列将都不会得到执行;实质上关闭的方法都是在里面跑出一个中断异常interrupt
线程池工作原理
以上大致是线程池的四种常用类型,同时我们也可以自定义自己的线程池ThreadPoolExecutor,上面的四种也是由它引化而出的,想要了解该线程池,必须了解线程池的工作原理,ThreadPoolExecutor构造方法如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 线程池核心数量,也就是线程池最小数量
maximumPoolSize: 线程池线程最大数量
keepAliveTime: 报活时间,简而言之就是空闲线程存活时间,如果任务过多并且处理时间短而频繁,就可以调大这个值,减少销毁空闲线程
uint: keepAliveTime的时间单位,有:天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS)和纳秒(NANOSECONDS)
workQueue: 缓存任务队列,当线程数大于核心线程时,而缓冲队列没有满时,将会把新来的任务加入到缓存队列里面去;缓存队列有多种:
(1)ArrayBlockingQueue,是一种基于数组的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行操作;
(2)LinkedBlockingQueue,是一个基于链表的阻塞队列,此队列也按FIFO (先进先出)对元素进行操作,吞吐量通常要高于ArrayBlockingQueue, Executors.newFixedThreadPool()使用了这种队列;
(3)SynchronousQueue;是一种不存储元素的阻塞队列,每个插入操作必须等另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,Executors.newCachedThreadPool使用了这个队列;
(4)PriorityBlockingQueue,是一种具有优先权的阻塞队列,优先级大的任务可以先执行,用户由此可以控制任务的执行顺序。这四种阻塞队列都有自己的使用场景,用户可以根据需要自己决定使用。
threadFactory: threadFactory 创建新线程时使用的工厂,threadFactory有两种选择:
(1)DefaultThreadFactory,将创建一个同线程组且默认优先级的线程;
(2)PrivilegedThreadFactory,使用访问权限创建一个权限控制的线程。ThreadPoolExecutor默认采用DefaultThreadFactory
handler: 当超出最大线程数和工作队列最大容量时,线程池采用的处理策略:
(1)ThreadPoolExecutor.AbortPolicy(),将抛出RejectedExecutionException异常;
(2)ThreadPoolExecutor.CallerRunsPolicy(),将重试添加当前的任务,重复调用execute()方法;
(3)ThreadPoolExecutor.DiscardOldestPolicy(),将抛弃旧任务;
(4)ThreadPoolExecutor.DiscardPolicy,将直接抛弃任务。ThreadPoolExecutor默认采用AbortPolicy。
工作原理:
一个任务必须通过execute(Runnable)提交任务,执行是Runnable里面的run方法,当一个任务被添加到线程池里面去的时候,其处理:
1. 如果线程数量小于corePoolSize,则创建新线程处理任务
2. 如果线程数等于corePoolSize,但是WorkQueue没有装满,则会把任务加入到缓存队列
3. 如果线程数等于corePoolSize并且WorkQueue已经装满,数量小于maximumPoolSize,则会创建新线程来处理任务
4. 如果corePoolSize、workQueue和maximumPoolSize都满了,则采取handler的处理策略
5. 当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
Callable接口:
Java5版本以后,提供了可以返回String参数的线程接口Callable;实质上返回参数是从它的call()方法中返回的
使用方法:
1. 用法类似于Runnable接口,编写类实现该接口;
2. 使用ExecutorService.Submit()提交线程并执行;
3. 返回结果将保存在Future对象里面,通过它的get方法可以获取结果
get是一个阻塞函数,它里面还有其他方法idDone判断是否执行完成
private static class myThread implements Callable<String>{
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return "String";
}
}
public static void main(String[] args){
ExecutorService es = Executors.newSingleThreadExecutor();
Future<String> str = es.submit(new myThread());
try {
String s = str.get();
System.out.println(s);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
后台线程:
前台线程结束,后台线程就结束;反之则不然;
设置后台线程方法:
1. 设置当前线程为后台线程:Thread里面的方法 public final void setDaemon(boolean on);凡是后台线程创建的线程均属于后台线程
2. 通过ThreadFactory设置线程池里面产生的都是后台线程
ExecutorService es = Executors.newCachedThreadPool(new DaemonThreadFactory());没看见在哪个包
可以使用public final boolean isDaemon()查看是否为后台程序;
在后台线程程序中放置try-catch-finally代码块,不一定能保证finally能够执行,因为你并不能确定程序何时停止,一旦非后程序全部结束,JVM就会关闭掉所有的后台程序
线程中捕获异常:
一般情况下,自己线程产生的异常只能自己捕获处理;但是有时候当前线程的异常逃逸后,其他线程也无法捕获处理,只能任由异常传输至控制台导致程序挂掉;为了解决这一问题,Java5提供了一套线程当中捕获异常的方法;该方法必须配合Executors使用也就是线程池,主要是在创建线程时,要设置thread的异常处理器
1. 创建一个异常处理器,该处理器中重写异常处理方法(该方法即是当前线程因为异常逃逸后导致线程死亡时使用它)
public class MyUncatchThreadHandler implements Thread.UncaughtExceptionHandler{
//异常逃逸时调用
@Override
public void uncaughtException(Thread t, Throwable e) {
// TODO Auto-generated method stub
System.out.println("Thread: " + t.toString());
System.out.println("Exception: " + e.toString());
}
}
2. 创建一个异常工厂来实现上面的异常处理器
public class MyUncatchThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable arg0) {
// TODO Auto-generated method stub
Thread t = new Thread(arg0);
//设置线程的异常处理器
t.setUncaughtExceptionHandler(new MyUncatchThreadHandler());
return t;
}
3. 编写测试线程(注释掉try-catch后产生异常逃逸,会执行撒谎那个面的uncaughtException方法)
public class ThreadTest implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
Thread t = Thread.currentThread();
System.out.println("ThreadTest: curretThread = " + t);
try {
throw new RuntimeException("我抛出了一个异常");
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("抓到异常了");
}
}
}
4. 最后测试:
ExecutorService es = Executors.newCachedThreadPool(new MyUncatchThreadFactory());
es.execute(new ThreadTest());;
es.shutdown();
5. 如果要许多线程都要设置成相同的异常处理器的话,那建议在Thread里面设置一个默认的控制器,第四步骤可以改为:
Thread.setDefaultUncaughtExceptionHandler(new ExecutorTest.MyUncatchThreadHandler());
ExecutorService es = Executors.newCachedThreadPool();
es.execute(new ExecutorTest.ThreadTest());;
es.shutdown();