java多线程和并发编程学习总结 ----基础篇4
四 java 5.0 以后对多线程的支持
在Java 5.0之前Java里的多线程编程主要是通过Thread类,Runnable接口,Object对象中的wait()、notify()、 notifyAll()等方法和synchronized和volatile等关键词来实现的。Java提供的这些工具虽然能在大多数情况下解决对共享资源的管理和线程间的调度,但还是存在一些缺陷。下面来看看Java对多线程的改进。
有了前面的基础和理解之后,对Java5.0对多线程的改进就很容易理解了。这里需要搞清楚 3个包,2个接口,1个任务执行框架。
3个新加入的多线程包:
- java.util.concurrent包含了常用的多线程工具,是新的多线程工具的主体。
- java.util.concurrent.atomic包含了不用加锁情况下就能改变值的原子变量,比如说AtomicInteger提供了addAndGet()方法。Add和Get是两个不同的操作,为了保证别的线程不干扰,以往的做法是先锁定共享的变量,然后在锁定的范围内进行两步操作。但用AtomicInteger.addAndGet()就不用担心锁定的事了,其内部实现保证了这两步操作是在原子量级发生的,不会被别的线程干扰。
- java.util.concurrent.locks包包含锁定的工具。
2个接口:Callable 和 Future接口
- Callable规定的方法是call(),而Runnable规定的方法是run().
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
- call()方法可抛出异常,而run()方法是不能抛出异常的。
- 运行Callable任务可拿到一个Future对象,通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
package java.util.concurrent;
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
由源码可以看出,Callable接口有唯一一个执行任务的call()方法,这个call()方法有返回值 V(泛型) 和异常throws抛出。package java.util.concurrent;
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
/**
* Returns <tt>true</tt> if this task was cancelled before it completed
* normally.
*
* @return <tt>true</tt> if this task was cancelled before it completed
*/
boolean isCancelled();
/**
* Returns <tt>true</tt> if this task completed.
*
* Completion may be due to normal termination, an exception, or
* cancellation -- in all of these cases, this method will return
* <tt>true</tt>.
*
* @return <tt>true</tt> if this task completed
*/
boolean isDone();
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
*/
V get() throws InterruptedException, ExecutionException;
/**
* Waits if necessary for at most the given time for the computation
* to complete, and then retrieves its result, if available.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
* @throws TimeoutException if the wait timed out
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
从源码我们也可以发现 Future是一个监视任务执行的接口,其中有判断任务是否取消(cancel)或者完成(IsDone)的方法,返回的都是boolean值,还有一个最重要和常用的抽象方法就是get()方法,我们可以发现 ,该方法返回一个泛型类型,并有异常抛出。该方法返回的泛型就是Callable接口的call()方法返回的结果。1个新任务执行框架
package java.util.concurrent;
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the <tt>Executor</tt> implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution.
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
- submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future对象
- invokeAll(collection of tasks):批处理任务集合,并返回一个代表这些任务的Future对象集合
- shutdown():在完成已提交的任务后关闭服务,不再接受新任务
- shutdownNow():停止所有正在执行的任务并关闭服务。
- isTerminated():测试是否所有任务都执行完毕了。
- isShutdown():测试是否该ExecutorService已被关闭
- schedule(task, initDelay): 安排所提交的Callable或Runnable任务在initDelay指定的时间后执行。
- scheduleAtFixedRate():安排所提交的Runnable任务按指定的间隔重复执行
- scheduleWithFixedDelay():安排所提交的Runnable任务在每次执行完后,等待delay所指定的时间后重复执行。
下面看个例子程序:一个典型的网络服务器模型程序
package org.bupt.qyl;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class Server {
//Socket连接
private static final String HOST = "127.0.0.1";
private static final int PORT = 19527;
//private ThreadPoolExecutor serverThreadPool = null;
private ExecutorService pool = null;
private ServerSocket serverListenSocket = null;
private int times = 5;
public void start() {
//创建线程池
pool = Executors.newFixedThreadPool(10);
//建立监听端口
try {
serverListenSocket = new ServerSocket(PORT);
serverListenSocket.setReuseAddress(true);
System.out.println("I'm listening");
while (times-- > 0) {
Socket socket = serverListenSocket.accept();
String welcomeString = "hello";
//serverThreadPool.execute(new ServiceThread(socket, welcomeString));
pool.execute(new ServiceThread(socket));
}
} catch (IOException e) {
e.printStackTrace();
}
//释放线程到线程池
cleanup();
}
//服务完毕,释放线程到线程池
public void cleanup() {
if (null != serverListenSocket) {
try {
serverListenSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//serverThreadPool.shutdown();
pool.shutdown();
}
public static void main(String args[]) {
Server server = new Server();
server.start();
}
}
/**
*
* 服务器线程
*
*/
class ServiceThread implements Runnable, Serializable {
private static final long serialVersionUID = 0;
private Socket connectedSocket = null;
private String helloString = null;
private static int count = 0;
private static ReentrantLock lock = new ReentrantLock();
public ServiceThread(Socket socket) {
connectedSocket = socket;
}
public void run() {
//统计访问量
increaseCount();
int curCount = getCount();
helloString = "hello, id = " + curCount + "rn";
//为每个客户端访问开启一个单线程线程执行器,并提交,让线程运行服务(返回欢迎信息)
ExecutorService executor = Executors.newSingleThreadExecutor();
//提交了一个Callable的任务,任务执行完之后,返回future
Future future = executor.submit(new TimeConsumingTask());
DataOutputStream dos = null;
//写入数据给客户端
try {
dos = new DataOutputStream(connectedSocket.getOutputStream());
dos.write(helloString.getBytes());
try {
dos.write("let's do soemthing other.rn".getBytes());
//获取 任务执行完之后的返回future
String result= (String) future.get();
dos.write(result.getBytes());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != connectedSocket) { //关掉socket连接
try {
connectedSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != dos) { //关掉数据输出流
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭执行器
executor.shutdown();
}
}
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();
}
}
}
//实现一个Callable的任务
class TimeConsumingTask implements Callable {
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";
}
}
该网络服务器模型将如下:
1.建立监听端口,创建线程池。
2.发现有新连接,使用线程池来执行服务任务。
3. 服务完毕,释放线程到线程池。
程序功能讲解:
程序中首先创建线程池以及初始化监听端口;
然后运行服务器线程;
在服务器线程ServiceThread维护一个count来记录服务线程被调用的次数。每当服务任务被调用一次时,count的值自增1,细的重入锁ReentrantLock。使用ReentrantLock保证代码线程安全。因此ServiceThread提供一个increaseCount和getCount的方法,分别将count值自增1和取得该count值。由于可能多个线程存在竞争,同时访问count,因此需要加锁机制,在Java 5之前,我们只能使用synchronized来锁定。Java 5中引入了性能更加粒度更细的重入锁ReentrantLock。我们使用ReentrantLock保证代码线程安全。接着在服务线程在开始给客户端打印一个欢迎信息。
同时使用ExecutorService的submit方法提交一个Callable的任务,返回一个Future接口的引用。这种做法对费时的任务非常有效,submit任务之后可以继续执行下面的代码,然后在适当的位置可以使用Future的get方法来获取结果,如果这时候该方法已经执行完毕,则无需等待即可获得结果,如果还在执行,则等待到运行完毕。声明TimeConsumingTask的时候使用了String做为类型参数。必须实现Callable接口的call函数,其作用类似与Runnable中的run函数,在call函数里写入要执行的代码,其返回值类型等同于在类声明中传入的类型值。
在这段程序中,提交了一个Callable的任务,然后程序不会堵塞,而是继续执行dos.write("let's do soemthing other".getBytes());当程序执行到Stringresult = future.get()时如果call函数已经执行完毕,则取得返回值,如果还在执行,则等待其执行完毕。