java多線程和併發編程學習總結 ----基礎篇4


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 5.0裏新加入了三個多線程包java.util.concurrent, java.util.concurrent.atomic, java.util.concurrent.locks.

  • java.util.concurrent包含了常用的多線程工具,是新的多線程工具的主體。


  • java.util.concurrent.atomic包含了不用加鎖情況下就能改變值的原子變量,比如說AtomicInteger提供了addAndGet()方法。AddGet是兩個不同的操作,爲了保證別的線程不干擾,以往的做法是先鎖定共享的變量,然後在鎖定的範圍內進行兩步操作。但用AtomicInteger.addAndGet()就不用擔心鎖定的事了,其內部實現保證了這兩步操作是在原子量級發生的,不會被別的線程干擾。

  • java.util.concurrent.locks包包含鎖定的工具。


2個接口:Callable  Future接口


   Callable是類似於Runnable的接口,實現Callable接口的類和實現Runnable的類都是可被其它線程執行的任務。CallableRunnable有幾點不同:

  • Callable規定的方法是call(),而Runnable規定的方法是run().

  • Callable的任務執行後可返回值,而Runnable的任務是不能返回值的。

  • call()方法可拋出異常,而run()方法是不能拋出異常的。

  • 運行Callable任務可拿到一個Future對象,通過Future對象可瞭解任務執行情況,可取消任務的執行,還可獲取任務執行的結果。

下面是Callable接口的源碼:
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拋出。


下面看看Future接口的源碼:
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個新任務執行框架



 Java 5.0之前啓動一個任務是通過調用Thread類的start()方法來實現的,任務的提交和執行是同時進行的,如果你想對任務的執行進行調度或是控制同時執行的線程數量就需要額外編寫代碼來完成。

在5.0改進以後,Java裏提供了一個新的任務執行架構使你可以輕鬆地調度和控制任務的執行,並且可以建立一個類似數據庫連接池的線程池來執行任務。


這個架構主要有三個接口和其相應的具體類組成。這三個接口是Executor, ExecutorServiceScheduledExecutorService,讓我們先用一個圖來顯示它們的關係:





Executor接口:是用來執行Runnable任務的

看看他的源碼:

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);
}

由源碼看出:它只定義一個方法:void execute(Runnable command):執行Ruannable類型的任務。



ExecutorService接口:ExecutorService繼承了Executor的方法,提供了執行Callable任務和中止任務執行的服務

源碼比較多,這裏我就不貼出源碼了,還是建議多看看源碼。其實看源碼和寫博客差不多,收穫頗多!

其定義的方法主要有:

  • submit(task):可用來提交CallableRunnable任務,並返回代表此任務的Future對象
  • invokeAll(collection of tasks):批處理任務集合,並返回一個代表這些任務的Future對象集合
  • shutdown():在完成已提交的任務後關閉服務,不再接受新任務
  • shutdownNow():停止所有正在執行的任務並關閉服務。
  • isTerminated():測試是否所有任務都執行完畢了。
  • isShutdown():測試是否該ExecutorService已被關閉

ScheduledExecutorService接口: 提供了按時間安排執行任務的功能

ExecutorService的基礎上,ScheduledExecutorService提供了按時間安排執行任務的功能,它提供的方法主要有:

  • schedule(task, initDelay): 安排所提交的CallableRunnable任務在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提供一個increaseCountgetCount的方法,分別將count值自增1和取得該count值。由於可能多個線程存在競爭,同時訪問count,因此需要加鎖機制,在Java 5之前,我們只能使用synchronized來鎖定。Java 5中引入了性能更加粒度更細重入鎖ReentrantLock。我們使用ReentrantLock保證代碼線程安全。接着在服務線程在開始給客戶端打印一個歡迎信息。

 

同時使用ExecutorServicesubmit方法提交一個Callable的任務,返回一個Future接口的引用。這種做法對費時的任務非常有效,submit任務之後可以繼續執行下面的代碼,然後在適當的位置可以使用Futureget方法來獲取結果,如果這時候該方法已經執行完畢,則無需等待即可獲得結果,如果還在執行,則等待到運行完畢。聲明TimeConsumingTask的時候使用了String做爲類型參數。必須實現Callable接口的call函數,其作用類似與Runnable中的run函數,在call函數裏寫入要執行的代碼,其返回值類型等同於在類聲明中傳入的類型值。


在這段程序中,提交了一個Callable的任務,然後程序不會堵塞,而是繼續執行dos.write("let's do soemthing other".getBytes());當程序執行到Stringresult = future.get()時如果call函數已經執行完畢,則取得返回值,如果還在執行,則等待其執行完畢。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章