一 并发的优点
并发的优点大致可以分为两个方面:“速度”和“代码设计”。
1 速度的提升
首先,我们需要知道并发与并行的区别,具体可以看这篇博客:http://blog.csdn.net/yy_james/article/details/71481467
现在的计算机都是多处理器,将一个程序的不同模块分布在不同的CPU上执行,显然可以提高整个程序的运行效率。但是这里需要注意的是,“并发”通常是提高运行在单个处理器上的程序的性能。
这听起来不太对,在同一个CPU上,顺序运行一个程序相比运行一个包含多个部分的并发程序,少了很多任务切换的代价,因为对于并发程序,CPU需要在不通的任务之间切换。所以直观感觉起来,对于单CPU来讲,顺序程序的执行效率更高。
但是有一个问题就是:“阻塞”。对于顺序执行的程序,如果由于某些原因,使程序进入了阻塞状态,那么整个程序都会停止不前;并发程序则不一样,如果一个任务被阻塞了,程序的其他任务还可以继续执行。所以,并发可以大幅度的提高单个CPU的使用效率。
Java中实现并发的方式是线程。与进程相比,线程的创建、管理消耗的资源更少,所以更适合并发程序。但线程也有问题,由于同一个进程的线程共享内存、IO这些资源,所以需要程序员控制这些资源不会同时被多个线程访问。
2 改进代码设计
线程可以使程序员创建更加松散耦合的设计。通俗点说,针对某些复杂的需求,可以将多个任务分配给多个单独的线程来执行;而不需要在一个线程中处理不同的任务(无论是在逻辑上还是代码编写上,这种方法都有问题)。
二 java线程的实现
1 定义一个任务
定义任务的方法有两种,一是实现Runnable接口,二是继承Thread类。无论使用哪种方法,核心都是要重写run()方法。run()方法中的代码,将会在线程开始的时候运行。
(1)实现Runnable接口
public class LiftOffS implements Runnable {
@Override
public void run() {
while(true ){
System.out.print("sleep");;
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(2)继承Thread类
public class SimpleThread extends Thread {
@Override
public void run() {
while(true ){
System.out.print("sleep");;
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2 使用线程驱动任务
使用线程驱动任务的方法有两种,一种是使用Thread类,一种是使用Executor,推荐使用第二种,因为Executor便于对线程的管理,而且更加安全(防止任务访问处于不稳定状态的对象)。
(1)Thread驱动(驱动上面的LiftOffS 类的对象)
pulbic static void main(String[] args){
Thread t = new Thread(new LiftOffS());
t.start();
}
(2)Executor驱动
如下,Executor可以一次驱动多个线程(一个线程池的概念),Executor可以有多种实现方式,常用的有以下几种。
下面代码中使用的CachedThreadPool,当一个新的任务被提交,线程池中没有可用的线程时,线程池就会新建一个线程,使用完的线程会被回收到线程池;
FixedThreadPool是固定大小的线程池,线程池在创建时,通过构造函数的参数指定线程池中线程的个数,当新任务得不到线程时,它将会被阻塞,直到线程池中有可用的线程;
SingleThreadPool可以认为是线程个数为1的FixedThreadPool。
ExecutorService executor = Executors.newCachedThreadPool();
for(int i=0; i<5; i++){
executor.execute(new LiftOffR());
}
executor.shutdown();
3 创建有返回值的线程
上面的线程是一个独立的任务,不具有返回值,如果希望一个线程具有返回值,那么定义任务时需要实现Cllable接口,而不是Runnable接口。
(1)定义任务,Callable接口具有类型参数的泛型,表示返回值的类型
public class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id) {
this.id = id;
}
@Override
public String call() throws Exception {
return "Task result from task" + id;
}
}
(2)驱动任务
public static void callbackTest(){
ArrayList<Future<String>> result = new ArrayList<Future<String>>();
ExecutorService executor = Executors.newCachedThreadPool();
for(int i=0; i<5; i++){
result.add(executor.submit(new TaskWithResult(i)));
}
executor.shutdown();
for(Future<String> fs : result){
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
在驱动任务时,Executor使用的是submit()方法,而不是execute()方法。
该方法返回一个Future<?>对象,可以通过Future的isDone()方法检查线程是否已经产生返回值,如果产生返回值,则使用Future的get()方法得到返回值;
也可以不使用isDone()方法,直接调用Future的get()方法,当线程还未产生返回值时,get()方法会被阻塞。
4 线程休眠
线程休眠所使用的是sleep()方法,该方法中断线程执行给定的时间。
//旧方法
Thread.sleep(1000); //毫秒
//新方法
TimeUnit.MILLISECONDS.sleep(1000); //毫秒
5 让步
线程的让步使用yield()方法实现,这个方法给线程调度机制一个暗示:我的工作已经做的差不多了,可以让别的线程使用CPU了。
但是这只是一个暗示,没有机制保证其必须被采纳。
Thread.yield();
6 优先级
线程中可以使用setPriority()方法设置优先级,也可以使用getPriority()方法得到优先级。
java中常用的优先级有三种(int型):Thread.MIN_PRIORITY,Thread.MAX_PRIORITY,Thread.NORM_PRIORITY。
调用方法:
Thread.setPriority(Thread.MIN_PRIORITY);
7 后台线程(daemon)
后台线程是指在程序运行的时候在后台提供一种通用服务线程,并且不是程序中不可或缺的部分。因此,当所有非后台进程结束的时候,整个程序也就结束了,同时会杀死进程中的所有后台线程(即使任务的run()方法中有finally块,也不会执行)。
此外,如果一个线程是后台线程,那么它创建的任何线程都是后台线程。
//设置后台线程
Thread t = new Thread(new Simple()); //Simple是一个实现了Runnable接口的类
t.setDaemon(true);
t.start();
if(t.isDaemon()){ //判断线程是否是后台线程
System.out.println("后台线程");
}
8 join
一个线程可以在另一个线程上调用join()方法,效果是等待一段时间,知道第二个线程执行结束后,第一个线程才继续执行。
join()也可以带上一个超时参数,这样,如果目标线程在超时时间内还没有结束,join()也可以继续返回。
public class Joiner extends Thread{
private Simple s;
public Joiner(Simple s){
this.s = s;
}
public void run(){
s.join();
doOther();
}
public static void main(String[] args){
Simple s = new Simple();
Thread t1 = new Thread(s); //Simple是一个实现了Runnable接口的类
Thread t2 = new Thread(new Joiner(s));
t1.start();
t2.start();
}
}
9 捕获异常
由于异常是无法在线程之间传递的,所以,由main()方法创建的线程,在出现异常时,无法在main()中捕获,异常信息会直接被打印在控制台中,只有在线程内部才能捕获。
不过java提供了方法,它允许在每个Thread对象上增加一个异常处理器,处理那些未被捕获的异常。
(1)首先,需要自己定义一个处理异常的类,这个类需要实现UncaughtExceptionHandler接口的方法。
public class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught "+e);
}
}
(2)继承线程工厂,配合Executor为每个线程增加异常处理器
public class HandlerThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
System.out.println(this+" creating new thread");
Thread t = new Thread(r);
System.out.println("created "+t);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println("en="+t.getUncaughtExceptionHandler());
return t;
}
}
(3)使用线程驱动任务
public class ExceptionThread implements Runnable {
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("en="+t.getUncaughtExceptionHandler());
throw new RuntimeException();
}
public static void main(String[] args){
/* 可以通过这种方式,设置默认的UncaughtExceptionHandler
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
ExecutorService executor = Executors.newCachedThreadPool();
*/
ExecutorService executor = Executors.newCachedThreadPool(new HandlerThreadFactory());
executor.execute(new ExceptionThread());
executor.shutdown();
}
}