java多线程学习(一)

来源:互联网 发布:booking.it 编辑:程序博客网 时间:2024/06/05 21:11

一 并发的优点

并发的优点大致可以分为两个方面:“速度”和“代码设计”。

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 {@Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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 {@Overridepublic void uncaughtException(Thread t, Throwable e) {System.out.println("caught "+e);}}


(2)继承线程工厂,配合Executor为每个线程增加异常处理器


public class HandlerThreadFactory implements ThreadFactory {@Overridepublic 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 {@Overridepublic void run() {Thread t = Thread.currentThread();System.out.println("en="+t.getUncaughtExceptionHandler());throw new RuntimeException();}public static void main(String[] args){/* 可以通过这种方式,设置默认的UncaughtExceptionHandlerThread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());ExecutorService executor = Executors.newCachedThreadPool();*/ExecutorService executor = Executors.newCachedThreadPool(new HandlerThreadFactory());executor.execute(new ExceptionThread());executor.shutdown();}}


 

1 0
原创粉丝点击