java5多线程之学习笔记一

Java5增加了新的类库并发集java.util.concurrent,该类库为并发程序提供了丰富的API多线程编程在Java 5中更加容易,灵活。

Java 5.0里新加入了三个多线程包:java.util.concurrent, java.util.concurrent.atomic, java.util.concurrent.locks.
java.util.concurrent包含了常用的多线程工具,是新的多线程工具的主体。
java.util.concurrent.atomic包含了不用加锁情况下就能改变值的原子变量,比如说AtomicInteger提供了addAndGet()方法。Add和Get是两个不同的操作,为了保证别的线程不干扰,以往的做法是先锁定共享的变量,然后在锁定的范围内进行两步操作。但用AtomicInteger.addAndGet()就不用担心锁定的事了,其内部实现保证了这两步操作是在原子量级发生的,不会被别的线程干扰。
java.util.concurrent.locks 包含锁定的工具。

1.Callable 和 Future接口
新引入的Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。Callable和Runnable有几点不同:

Callable规定的方法是call(),而Runnable规定的方法是run().
Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
call()方法可抛出异常,而run()方法是不能抛出异常的。
运行Callable任务可拿到一个Future对象,通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。 
下面是一个简单的Callable例子。它实现了Callable<String>,String将是call方法的返回值类型,并且可以抛出异常。


public class CallSample implements Callable<String> {   
  

    private int aInt;  
 
    public CallSample(int aInt) {  
        this.aInt = aInt;  
    }  
 
    public String call() throws Exception {  
        boolean resultOk = false;  
        if (aInt == 0) {  
            resultOk = true;  
        } else if (aInt == 1) {  
            while (true) {   
                System.out.println("looping....");  
                Thread.sleep(3000);  
            }  
        } else {  
            throw new Exception("Callable terminated with Exception!");  
        }  
        return resultOk?"Task done":"Task failed";  
    }   
 
public class CallSample implements Callable<String> {
 
 private int aInt;
 public CallSample(int aInt) {
  this.aInt = aInt;
 }
 public String call() throws Exception {
  boolean resultOk = false;
  if (aInt == 0) {
   resultOk = true;
  } else if (aInt == 1) {
   while (true) {
    System.out.println("looping....");
    Thread.sleep(3000);
   }
  } else {
   throw new Exception("Callable terminated with Exception!");
  }
  return resultOk?"Task done":"Task failed";
 }
}

定义一个测试程序来运行下。建立一个拥有固定3个线程的线程池,分别执行正常结束、无限循环、抛出异常这3种情况的Callable
Java代码
public class Executor {  
    public static void main(String[] args) {  
 
        // 分别给定3种情况的Callable  
        CallSample call1 = new CallSample(0);  
        CallSample call2 = new CallSample(1);  
        CallSample call3 = new CallSample(2);  
 
        ExecutorService es = Executors.newFixedThreadPool(3);  
 
        Future<String> future1 = es.submit(call1);  
        Future<String> future2 = es.submit(call2);  
        Future<String> future3 = es.submit(call3);  
        try {  
            System.out.println(future1.get());  
            Thread.sleep(3000);  
            System.out.println("Thread 2 terminated? :" + future2.cancel(true));  
            System.out.println(future3.get());  
        } catch (ExecutionException ex) {  
            ex.printStackTrace();  
        } catch (InterruptedException ex) {  
            ex.printStackTrace();  
        }  
    }  

测试结果:
looping....
call3
Task done
Thread 2 terminated? :true
Task failed
2.新的线程执行方式
在Java 5.0之前启动一个任务是通过调用Thread类的start()方法来实现的,任务的提交和执行是同时进行的,如果你想对任务的执行进行调度或是控制同时执行的线程数量就需要额外编写代码来完成。5.0里提供了一个新的任务执行架构使你可以轻松地调度和控制任务的执行,并且可以建立一个类似数据库连接池的线程池来执行任务。这个架构主要有三个接口和其相应的具体类组成。这三个接口是Executor, ExecutorService和ScheduledExecutorService。
Executor接口:
是用来执行Runnable任务的,它只定义一个方法:execute(Runnable command):执行Ruannable类型的任务
ExecutorService接口:
ExecutorService继承了Executor的方法,并提供了执行Callable任务和中止任务执行的服务,其定义的方法主要有:
submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future对象
invokeAll(collection of tasks):批处理任务集合,并返回一个代表这些任务的Future对象集合
shutdown():在完成已提交的任务后关闭服务,不再接受新任务
shutdownNow():停止所有正在执行的任务并关闭服务。
isTerminated():测试是否所有任务都执行完毕了。
isShutdown():测试是否该ExecutorService已被关闭.
下面举例来说明

 
一个典型的网络服务器模型如下:

1. 建立监听端口。

2. 发现有新连接,接受连接,启动线程,执行服务线程。

3. 服务完毕,关闭线程。

这个模型在大部分情况下运行良好,但是需要频繁的处理用户请求而每次请求需要的服务又是简短的时候,系统会将大量的时间花费在线程的创建销毁。Java 5的线程池克服了这些缺点。通过对重用线程来执行多个任务,避免了频繁线程的创建与销毁开销,使得服务器的性能方面得到很大提高。利用线程池的网络服务器模型将如下:

1. 建立监听端口,创建线程池。

2. 发现有新连接,使用线程池来执行服务任务。

3. 服务完毕,释放线程到线程池。

下面详细介绍如何使用Java 5的concurrent包提供的API来实现该服务器。

初始化包括创建线程池以及初始化监听端口。创建线程池可以通过调用java.util.concurrent.Executors类里的静态方法newChahedThreadPool或是newFixedThreadPool来创建,也可以通过新建一个java.util.concurrent.ThreadPoolExecutor实例来执行任务。这里采用newFixedThreadPool方法来建立线程池。

ExecutorService pool = Executors.newFixedThreadPool(10);
表示新建了一个线程池,线程池里面有10个线程为任务队列服务。

使用ServerSocket对象来初始化监听端口。

 


private static final int PORT = 19527;
serverListenSocket = new ServerSocket(PORT);
serverListenSocket.setReuseAddress(true);
serverListenSocket.setReuseAddress(true);
 

 


当有新连接建立时,accept返回时,将服务任务提交给线程池执行。

 

 

while(true){
Socket socket = serverListenSocket.accept();
pool.execute(new ServiceThread(socket));

}
 


这里使用线程池对象来执行线程,减少了每次线程创建和销毁的开销。任务执行完毕,线程释放到线程池。


服务线程ServiceThread维护一个count来记录服务线程被调用的次数。每当服务任务被调用一次时,count的值自增1,因此ServiceThread提供一个increaseCount和getCount的方法,分别将count值自增1和取得该count值。由于可能多个线程存在竞争,同时访问count,因此需要加锁机制,在Java 5之前,我们只能使用synchronized来锁定。Java 5中引入了性能更加粒度更细的重入锁ReentrantLock。我们使用ReentrantLock保证代码线程安全。下面是具体代码:

 


private static ReentrantLock lock = new ReentrantLock ();
private static int count = 0;
private int getCount(){
 int ret = 0;
 try{
  lock.lock();
  ret = count;
 }finally{
  lock.unlock();
 }
 return ret;

private void increaseCount(){
 try{
  lock.lock();
  ++count;
 }finally{
  lock.unlock();
 }
}
 


服务线程在开始给客户端打印一个欢迎信息,

 


increaseCount();
int curCount = getCount();
helloString = "hello, id = " + curCount+"/r/n";
dos = new DataOutputStream(connectedSocket.getOutputStream());
dos.write(helloString.getBytes());
 


然后使用ExecutorService的submit方法提交一个Callable的任务,返回一个Future接口的引用。这种做法对费时的任务非常有效,submit任务之后可以继续执行下面的代码,然后在适当的位置可以使用Future的get方法来获取结果,如果这时候该方法已经执行完毕,则无需等待即可获得结果,如果还在执行,则等待到运行完毕。

 


ExecutorService executor = Executors.newSingleThreadExecutor();
Future <String> future = executor.submit(new TimeConsumingTask());
dos.write("let's do soemthing other".getBytes());
String result = future.get();
dos.write(result.getBytes());
其中TimeConsumingTask实现了Callable接口
class TimeConsumingTask implements Callable <String>{
 public String call() throws Exception {
  System.out.println
  ("It's a time-consuming task,
  you'd better retrieve your result in the furture");
  return "ok, here's the result: It takes me lots of time to produce this result";
 }
}
 


这里使用了Java 5的另外一个新特性泛型,声明TimeConsumingTask的时候使用了String做为类型参数。必须实现Callable接口的call函数,其作用类似与Runnable中的run函数,在call函数里写入要执行的代码,其返回值类型等同于在类声明中传入的类型值。在这段程序中,我们提交了一个Callable的任务,然后程序不会堵塞,而是继续执行dos.write("let's do soemthing other".getBytes());当程序执行到String result = future.get()时如果call函数已经执行完毕,则取得返回值,如果还在执行,则等待其执行完毕。

测试结果:

你可能感兴趣的:(java5多线程之学习笔记一)