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函數已經執行完畢,則取得返回值,如果還在執行,則等待其執行完畢。