第一篇:關于Java并發(fā)編程的總結和思考
關于Java并發(fā)編程的總結和思考
編寫優(yōu)質(zhì)的并發(fā)代碼是一件難度極高的事情。Java語言從第一版本開始內(nèi)置了對多線程的支持,這一點在當年是非常了不起的,但是當我們對并發(fā)編程有了更深刻的認識和更多的實踐后,實現(xiàn)并發(fā)編程就有了更多的方案和更好的選擇。本文是對并發(fā)編程的一點總結和思考,同時也分享了Java 5以后的版本中如何編寫并發(fā)代碼的一點點經(jīng)驗。
為什么需要并發(fā)
并發(fā)其實是一種解耦合的策略,它幫助我們把做什么(目標)和什么時候做(時機)分開。這樣做可以明顯改進應用程序的吞吐量(獲得更多的CPU調(diào)度時間)和結構(程序有多個部分在協(xié)同工作)。做過Java Web開發(fā)的人都知道,Java Web中的Servlet程序在Servlet容器的支持下采用單實例多線程的工作模式,Servlet容器為你處理了并發(fā)問題。
誤解和正解
最常見的對并發(fā)編程的誤解有以下這些:
-并發(fā)總能改進性能(并發(fā)在CPU有很多空閑時間時能明顯改進程序的性能,但當線程數(shù)量較多的時候,線程間頻繁的調(diào)度切換反而會讓系統(tǒng)的性能下降)
-編寫并發(fā)程序無需修改原有的設計(目的與時機的解耦往往會對系統(tǒng)結構產(chǎn)生巨大的影響)
-在使用Web或EJB容器時不用關注并發(fā)問題(只有了解了容器在做什么,才能更好的使用容器)
下面的這些說法才是對并發(fā)客觀的認識:
-編寫并發(fā)程序會在代碼上增加額外的開銷
-正確的并發(fā)是非常復雜的,即使對于很簡單的問題
-并發(fā)中的缺陷因為不易重現(xiàn)也不容易被發(fā)現(xiàn)
-并發(fā)往往需要對設計策略從根本上進行修改 并發(fā)編程的原則和技巧
單一職責原則
分離并發(fā)相關代碼和其他代碼(并發(fā)相關代碼有自己的開發(fā)、修改和調(diào)優(yōu)生命周期)。限制數(shù)據(jù)作用域
兩個線程修改共享對象的同一字段時可能會相互干擾,導致不可預期的行為,解決方案之一是構造臨界區(qū),但是必須限制臨界區(qū)的數(shù)量。使用數(shù)據(jù)副本
數(shù)據(jù)副本是避免共享數(shù)據(jù)的好方法,復制出來的對象只是以只讀的方式對待。Java 5的java.util.concurrent包中增加一個名為CopyOnWriteArrayList的類,它是List接口的子類型,所以你可以認為它是ArrayList的線程安全的版本,它使用了寫時復制的方式創(chuàng)建數(shù)據(jù)副本進行操作來避免對共享數(shù)據(jù)并發(fā)訪問而引發(fā)的問題。線程應盡可能獨立
讓線程存在于自己的世界中,不與其他線程共享數(shù)據(jù)。有過Java Web開發(fā)經(jīng)驗的人都知道,Servlet就是以單實例多線程的方式工作,和每個請求相關的數(shù)據(jù)都是通過Servlet子類的service方法(或者是doGet或doPost方法)的參數(shù)傳入的。只要Servlet中的代碼只使用局部變量,Servlet就不會導致同步問題。springMVC的控制器也是這么做的,從請求中獲得的對象都是以方法的參數(shù)傳入而不是作為類的成員,很明顯Struts 2的做法就正好相反,因此Struts 2中作為控制器的Action類都是每個請求對應一個實例。Java 5以前的并發(fā)編程
Java的線程模型建立在搶占式線程調(diào)度的基礎上,也就是說:
所有線程可以很容易的共享同一進程中的對象。能夠引用這些對象的任何線程都可以修改這些對象。為了保護數(shù)據(jù),對象可以被鎖住。
Java基于線程和鎖的并發(fā)過于底層,而且使用鎖很多時候都是很萬惡的,因為它相當于讓所有的并發(fā)都變成了排隊等待。
在Java 5以前,可以用synchronized關鍵字來實現(xiàn)鎖的功能,它可以用在代碼塊和方法上,表示在執(zhí)行整個代碼塊或方法之前線程必須取得合適的鎖。對于類的非靜態(tài)方法(成員方法)而言,這意味這要取得對象實例的鎖,對于類的靜態(tài)方法(類方法)而言,要取得類的Class對象的鎖,對于同步代碼塊,程序員可以指定要取得的是那個對象的鎖。
不管是同步代碼塊還是同步方法,每次只有一個線程可以進入,如果其他線程試圖進入(不管是同一同步塊還是不同的同步塊),JVM會將它們掛起(放入到等鎖池中)。這種結構在并發(fā)理論中稱為臨界區(qū)(critical section)。這里我們可以對Java中用synchronized實現(xiàn)同步和鎖的功能做一個總結:
只能鎖定對象,不能鎖定基本數(shù)據(jù)類型 被鎖定的對象數(shù)組中的單個對象不會被鎖定
同步方法可以視為包含整個方法的synchronized(this){ ? }代碼塊 靜態(tài)同步方法會鎖定它的Class對象 內(nèi)部類的同步是獨立于外部類的
synchronized修飾符并不是方法簽名的組成部分,所以不能出現(xiàn)在接口的方法聲明中 非同步的方法不關心鎖的狀態(tài),它們在同步方法運行時仍然可以得以運行 synchronized實現(xiàn)的鎖是可重入的鎖。
在JVM內(nèi)部,為了提高效率,同時運行的每個線程都會有它正在處理的數(shù)據(jù)的緩存副本,當我們使用synchronzied進行同步的時候,真正被同步的是在不同線程中表示被鎖定對象的內(nèi)存塊(副本數(shù)據(jù)會保持和主內(nèi)存的同步,現(xiàn)在知道為什么要用同步這個詞匯了吧),簡單的說就是在同步塊或同步方法執(zhí)行完后,對被鎖定的對象做的任何修改要在釋放鎖之前寫回到主內(nèi)存中;在進入同步塊得到鎖之后,被鎖定對象的數(shù)據(jù)是從主內(nèi)存中讀出來的,持有鎖的線程的數(shù)據(jù)副本一定和主內(nèi)存中的數(shù)據(jù)視圖是同步的。
在Java最初的版本中,就有一個叫volatile的關鍵字,它是一種簡單的同步的處理機制,因為被volatile修飾的變量遵循以下規(guī)則:
變量的值在使用之前總會從主內(nèi)存中再讀取出來。對變量值的修改總會在完成之后寫回到主內(nèi)存中。
使用volatile關鍵字可以在多線程環(huán)境下預防編譯器不正確的優(yōu)化假設(編譯器可能會將在一個線程中值不會發(fā)生改變的變量優(yōu)化成常量),但只有修改時不依賴當前狀態(tài)(讀取時的值)的變量才應該聲明為volatile變量。
不變模式也是并發(fā)編程時可以考慮的一種設計。讓對象的狀態(tài)是不變的,如果希望修改對象的狀態(tài),就會創(chuàng)建對象的副本并將改變寫入副本而不改變原來的對象,這樣就不會出現(xiàn)狀態(tài)不一致的情況,因此不變對象是線程安全的。Java中我們使用頻率極高的String類就采用了這樣的設計。如果對不變模式不熟悉,可以閱讀閻宏博士的《Java與模式》一書的第34章。說到這里你可能也體會到final關鍵字的重要意義了。
Java 5的并發(fā)編程
不管今后的Java向著何種方向發(fā)展或者滅忙,Java 5絕對是Java發(fā)展史中一個極其重要的版本,這個版本提供的各種語言特性我們不在這里討論(有興趣的可以閱讀我的另一篇文章《Java的第20年:從Java版本演進看編程技術的發(fā)展》),但是我們必須要感謝Doug Lea在Java 5中提供了他里程碑式的杰作java.util.concurrent包,它的出現(xiàn)讓Java的并發(fā)編程有了更多的選擇和更好的工作方式。Doug Lea的杰作主要包括以下內(nèi)容:
更好的線程安全的容器 線程池和相關的工具類 可選的非阻塞解決方案 顯示的鎖和信號量機制
下面我們對這些東西進行一一解讀。
原子類
Java 5中的java.util.concurrent包下面有一個atomic子包,其中有幾個以Atomic打頭的類,例如AtomicInteger和AtomicLong。它們利用了現(xiàn)代處理器的特性,可以用非阻塞的方式完成原子操作,代碼如下所示: /** ID序列生成器 */ public class IdGenerator {
private final AtomicLong sequenceNumber = new AtomicLong(0);
public long next(){
return sequenceNumber.getAndIncrement();
} } 顯示鎖
基于synchronized關鍵字的鎖機制有以下問題:
鎖只有一種類型,而且對所有同步操作都是一樣的作用 鎖只能在代碼塊或方法開始的地方獲得,在結束的地方釋放 線程要么得到鎖,要么阻塞,沒有其他的可能性
Java 5對鎖機制進行了重構,提供了顯示的鎖,這樣可以在以下幾個方面提升鎖機制:
可以添加不同類型的鎖,例如讀取鎖和寫入鎖 可以在一個方法中加鎖,在另一個方法中解鎖
可以使用tryLock方式嘗試獲得鎖,如果得不到鎖可以等待、回退或者干點別的事情,當然也可以在超時之后放棄操作 顯示的鎖都實現(xiàn)了java.util.concurrent.Lock接口,主要有兩個實現(xiàn)類:
ReentrantLock在讀操作很多寫操作很少時性能更好的一種重入鎖
對于如何使用顯示鎖,可以參考我的Java面試系列文章《Java面試題集51-70》中第60題的代碼。只有一點需要提醒,解鎖的方法unlock的調(diào)用最好能夠在finally塊中,因為這里是釋放外部資源最好的地方,當然也是釋放鎖的最佳位置,因為不管正常異常可能都要釋放掉鎖來給其他線程以運行的機會。
CountDownLatch
CountDownLatch是一種簡單的同步模式,它讓一個線程可以等待一個或多個線程完成它們的工作從而避免對臨界資源并發(fā)訪問所引發(fā)的各種問題。下面借用別人的一段代碼(我對它做了一些重構)來演示CountDownLatch是如何工作的。
import java.util.concurrent.CountDownLatch;/** * 工人類
* @author 駱昊
* */ class Worker {
private String name;
// 名字
private long workDuration;// 工作持續(xù)時間
/**
* 構造器
*/
public Worker(String name, long workDuration){
this.name = name;
this.workDuration = workDuration;
}
/**
* 完成工作
*/
public void doWork(){
System.out.println(name + “ begins to work...”);
try {
Thread.sleep(workDuration);// 用休眠模擬工作執(zhí)行的時間
} catch(InterruptedException ex){
ex.printStackTrace();
}
System.out.println(name + “ has finished the job...”);
} } /** * 測試線程
* @author 駱昊
* */ class WorkerTestThread implements Runnable {
private Worker worker;
private CountDownLatch cdLatch;
public WorkerTestThread(Worker worker, CountDownLatch cdLatch){
this.worker = worker;
this.cdLatch = cdLatch;
}
@Override
public void run(){
worker.doWork();
// 讓工人開始工作
cdLatch.countDown();
// 工作完成后倒計時次數(shù)減1
} } class CountDownLatchTest {
private static final int MAX_WORK_DURATION = 5000;// 最大工作時間
private static final int MIN_WORK_DURATION = 1000;// 最小工作時間
// 產(chǎn)生隨機的工作時間
private static long getRandomWorkDuration(long min, long max){
return(long)(Math.random()*(max1);// 如果有N個哲學家,最多只允許N-1人同時取叉子
}
/**
* 取得叉子
* @param index 第幾個哲學家
* @param leftFirst 是否先取得左邊的叉子
* @throws InterruptedException
*/
public static void putOnFork(int index, boolean leftFirst)throws InterruptedException {
if(leftFirst){
forks[index].acquire();
forks[(index + 1)% NUM_OF_PHILO].acquire();
}
else {
forks[(index + 1)% NUM_OF_PHILO].acquire();
forks[index].acquire();
}
}
/**
* 放回叉子
* @param index 第幾個哲學家
* @param leftFirst 是否先放回左邊的叉子
* @throws InterruptedException
*/
public static void putDownFork(int index, boolean leftFirst)throws InterruptedException {
if(leftFirst){
forks[index].release();
forks[(index + 1)% NUM_OF_PHILO].release();
}
else {
forks[(index + 1)% NUM_OF_PHILO].release();
forks[index].release();
}
} } /** * 哲學家
* @author 駱昊
* */ class Philosopher implements Runnable {
private int index;
// 編號
private String name;
// 名字
public Philosopher(int index, String name){
this.index = index;
this.name = name;
}
@Override
public void run(){
while(true){
try {
AppContext.counter.acquire();
boolean leftFirst = index % 2 == 0;
AppContext.putOnFork(index, leftFirst);
System.out.println(name + “正在吃意大利面(通心粉)...”);
// 取到兩個叉子就可以進食
AppContext.putDownFork(index, leftFirst);
AppContext.counter.release();
} catch(InterruptedException e){
e.printStackTrace();
}
}
} }
public class Test04 {
public static void main(String[] args){
String[] names = { “駱昊”, “王大錘”, “張三豐”, “楊過”, “李莫愁” };
// 5位哲學家的名字
//
ExecutorService es = Executors.newFixedThreadPool(AppContext.NUM_OF_PHILO);// 創(chuàng)建固定大小的線程池 //
for(int i = 0, len = nawww.tmdps.cnmes.length;i < len;++i){ //
es.execute(new Philosopher(i, names[i]));
// 啟動線程 //
} //
es.shutdown();
for(int i = 0, len = names.length;i < len;++i){
new Thread(new Philosopher(i, names[i])).start();
}
} }
現(xiàn)實中的并發(fā)問題基本上都是這三種模型或者是這三種模型的變體。
測試并發(fā)代碼
對并發(fā)代碼的測試也是非常棘手的事情,棘手到無需說明大家也很清楚的程度,所以這里我們只是探討一下如何解決這個棘手的問題。我們建議大家編寫一些能夠發(fā)現(xiàn)問題的測試并經(jīng)常性的在不同的配置和不同的負載下運行這些測試。不要忽略掉任何一次失敗的測試,線程代碼中的缺陷可能在上萬次測試中僅僅出現(xiàn)一次。具體來說有這么幾個注意事項:
不要將系統(tǒng)的失效歸結于偶發(fā)事件,就像拉不出屎的時候不能怪地球沒有引力。先讓非并發(fā)代碼工作起來,不要試圖同時找到并發(fā)和非并發(fā)代碼中的缺陷。編寫可以在不同配置環(huán)境下運行的線程代碼。
編寫容易調(diào)整的線程代碼,這樣可以調(diào)整線程使性能達到最優(yōu)。
讓線程的數(shù)量多于CPU或CPU核心的數(shù)量,這樣CPU調(diào)度切換過程中潛在的問題才會暴露出來。讓并發(fā)代碼在不同的平臺上運行。
通過自動化或者硬編碼的方式向并發(fā)代碼中加入一些輔助測試的代碼。Java 7的并發(fā)編程
Java 7中引入了TransferQueue,它比BlockingQueue多了一個叫transfer的方法,如果接收線程處于等待狀態(tài),該操作可以馬上將任務交給它,否則就會阻塞直至取走該任務的線程出現(xiàn)。可以用TransferQueue代替BlockingQueue,因為它可以獲得更好的性能。
剛才忘記了一件事情,Java 5中還引入了Callable接口、Future接口和FutureTask接口,通過他們也可以構建并發(fā)應用程序,代碼如下所示。
import java.util.ArrayList;import java.util.List;import java.util.concurrent.Callable;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;
public class Test07 {
private static final int POOL_SIZE = 10;
static class CalcThread implements Callable
private List
public CalcThread(){
for(int i = 0;i < 10000;++i){
dataList.add(Math.random());
}
}
@Override
public Double call()throws Exception {
double total = 0;
for(Double d : dataList){
total += d;
}
return total / dataList.size();
}
}
public static void main(String[] args){
List
ExecutorService es = Executors.newFixedThreadPool(POOL_SIZE);
for(int i = 0;i < POOLwww.tmdps.cnpute(){
int sum = 0;
if((end-start)< THRESHOLD){
// 當問題分解到可求解程度時直接計算結果
for(int i = start;i <= end;i++){
sum += i;
}
} else {
int middle =(start + end)>>> 1;
// 將任務一分為二
Calculator left = new Calculator(start, middle);
Calculator right = new Calculator(middle + 1, end);
left.fork();
right.fork();
// 注意:由于此處是遞歸式的任務分解,也就意味著接下來會二分為四,四分為八...sum = left.join()+ right.join();
// 合并兩個子任務的結果
}
return sum;
} }
public class Test08 {
public static void main(String[] args)throws Exception {
ForkJoinPool forkJoinPool = new ForkJoinPool();
Future
System.out.println(result.get());
} }
伴隨著Java 7的到來,Java中默認的數(shù)組排序算法已經(jīng)不再是經(jīng)典的快速排序(雙樞軸快速排序)了,新的排序算法叫TimSort,它是歸并排序和插入排序的混合體,TimSort可以通過分支合并框架充分利用現(xiàn)代處理器的多核特性,從而獲得更好的性能(更短的排序時間)。
第二篇:Java AWT編程總結
1.什么是GUI?
a)GUI是Graphics User Interface的全稱,意思是圖形用戶界面.2.為什么需要GUI?
a)圖形用戶界面能夠讓最終用戶通過鼠標拖動、單擊等動作就可以操作整個應用,從而提高應用的用戶體驗效果,使程序受到用戶的歡迎.3.Java通過AWT和SWING來完成GUI圖形用戶界面編程.4.AWT
a)AWT是SUN公司提供的一個基本的GUI類庫,被稱為抽象工具集(Abstract
Window-Toolkit),它為Java應用程序提供了基本的組件.b)AWT組件需要調(diào)用運行平臺的圖形界面來創(chuàng)建和平臺一致的對等體,所以AWT只
能使用所有平臺都支持的公共組件,因此AWT只能夠提供一些 常用的GUI組件.5.AWT的主要組成部分
a)Component,代表一個具體圖形表示能力的對象,可以在屏幕上顯示,并與用戶交互.通常我們把它稱為”組件”.b)MenuComponent,代表圖形界面的菜單.i.MenuBar,代表菜單條.ii.Menu,代表一個菜單項的集合.iii.MenuItem,代表一個菜單項.c)Container,代表一個AWT組件容器,可以盛裝其他Commponent組件,它繼承自
Component抽象類,本身也代表一個Component組件.i.Window,可獨立存在的頂級窗口.1.Frame,代表一個窗體.2.Dialog,代表一個對話框
a)FileDialog代表一個文件對話框,用于打開或保存文件.Panel,可容納其他組件,但不能獨立存在,必須被添加到其他容器中.ii.iii.ScrollPane,帶滾動條的容器.d)LayoutManager,布局管理器,表示容器管理其他組件的方式.i.ii.iii.iv.v.vi.FlowLayout,流式布局,類似于Window平臺記事本的文本布局方式.BorderLayout,邊框布局,只能盛裝5個組件,這5個組件分別位于邊框布局容器的東西南北中五個方位.GridLayout,網(wǎng)格布局,將組件以網(wǎng)格形式顯示在容器中.GridBagLayout,網(wǎng)格包布局,一種較為復雜的布局管理器,依賴GridBagConstraints來約束組件.CardLayout,卡片布局,以時間來管理容器內(nèi)的組件,將組件看作是一張張卡片,每次顯示最外面一張卡片(組件).BoxLayou,箱式布局,通常與Box容器結合使用.6.AWT 的事件
a)應用程序響應用戶的某個動作或請求,如用戶單擊了一下鼠標,用戶請求關閉應用
程序窗口等.b)AWT編程中,所有事件的處理都必須交給特定的對象來完成,我們將這個特定的對
象稱為事件監(jiān)聽器.c)AWT的事件處理機制是一種委派式的事件處理方式,通過將某個事件監(jiān)聽器注冊
到用戶指定的組件,當用戶進行某個操作并觸發(fā)指定事件時,應用程序會自動產(chǎn)生一個事件(Event)對象并作為參數(shù)傳給事件監(jiān)聽器中的事件處理器,然后由事件監(jiān)
聽器通知事件處理器來響應用戶,完成用戶的請求.d)不同的事件需要不同的事件監(jiān)聽器,不同的監(jiān)聽器需要實現(xiàn)不同的監(jiān)聽器接口.e)事件監(jiān)聽器接口:為某個特定事件定義了響應用戶請求的方法,當用戶將某個事件
監(jiān)聽器注冊到指定組件上以響應特定的事件時,則該事件監(jiān)聽器必須實現(xiàn)對應的事件監(jiān)聽器接口才能對用戶的請求進行有效處理.例如,用戶點擊了鼠標右鍵,希望打開某個應用程序的右鍵菜單,則注冊到該應用程序上的事件監(jiān)聽器必須實現(xiàn)鼠標事件監(jiān)聽器接口,并實現(xiàn)該接口內(nèi)部某些方法來完成用戶的請求.f)事件適配器,很多時候,我們只需要實現(xiàn)某個事件監(jiān)聽器接口中個別方法就能完成應用程序的實際需求,但實現(xiàn)該事件監(jiān)聽器接口的類必須實現(xiàn)該接口中所有的抽象方法,這會造成代碼的冗余.而事件適配器可以幫我們解決這個問題,事件適配器實現(xiàn)了所有的擁有多個抽象方法的事件監(jiān)聽器接口,并空實現(xiàn)了這些接口中所有的抽象方法,所謂空實現(xiàn),就是方法中沒有任何實現(xiàn)代碼,因此,我們可以通過繼承對應事件監(jiān)聽器接口的事件適配器抽象類,并實現(xiàn)我們感興趣的方法來完成應用需求即可.g)Java事件處理過程中主要涉及的三類對象
i.事件源,通常為普通組件.ii.事件,通常指用戶的某個操作,如單擊了一下鼠標,按了一下回車鍵.iii.事件監(jiān)聽器,負責監(jiān)聽事件源上所發(fā)生的事件,并作出響應.h)AWT事件監(jiān)聽器的實現(xiàn)形式
i.ii.內(nèi)部類形式 頂級類形式
iii.類本身作為事件監(jiān)聽器
iv.匿名內(nèi)部類形式
v.注:目前最為流行的事件監(jiān)聽器的實現(xiàn)形式是內(nèi)部類形式和匿名內(nèi)部類形式.7.AWT繪圖
a)AWT繪圖的實現(xiàn)過程.i.重寫畫布類的paint方法,繪圖圖形.ii.注冊事件監(jiān)聽器到指定的組件.iii.調(diào)用Component類的repaint方法繪制圖形.b)AWT實現(xiàn)繪圖主要涉及的對象
i.ii.c)Component類的子類Canvas類,它代表一個畫布.Graphics,代表一個畫筆,可以在Canvas的子類中繪制用戶自訂的圖形.Image類代表了位圖,它的一個主要的實現(xiàn)類BufferedImage是可以訪問圖形數(shù)據(jù)
緩沖區(qū),并可以返回一個Graphics對象來繪制該BuuferedImage.d)可以使用ImageIO工具類的ImageReader和ImageWriter讀寫磁盤上的位圖文件.8.AWT的優(yōu)缺點
a)AWT在許多非桌面環(huán)境,如嵌入式設備中有著自己的優(yōu)勢,它的主要優(yōu)點如下:i.ii.iii.iv.更少的內(nèi)存:對運行在有限環(huán)境中的GUI程序的開發(fā),是合適的。2.更少的啟動事件:由于AWT組件是本地由操作系統(tǒng)實現(xiàn)的。絕大多數(shù)的二進制代碼已經(jīng)在如系統(tǒng)啟動的時候被預裝載了,這降低了它的啟動事件。3.更好的響應:由于本地組件由操作系統(tǒng)渲染。4.成熟穩(wěn)定的:能夠正常工作并很少使你的程序崩潰。
b)同樣它也有不少的缺點
i.ii.iii.更少組件類型:表和樹這些重要的組件缺失了。它們是桌面應用程序中普遍使用的。2.缺乏豐富的組件特征:按鈕不支持圖片。3.無擴展性:AWT的組件是本地組件。JVM中的AWT類實例實際只是包含本地
組件的引用。唯一的擴展點是AWT的Canvas組件,可以從零開始創(chuàng)建自定義組
件。然而無法繼承和重用一個已有的AWT組件
9.AWT總結:AWT是SUN不推薦使用的工具集,實際開發(fā)中很少使用AWT而是使用SUN公司
和Netscape公司共同開發(fā)的一個新的用戶界面庫-Swing來開發(fā)GUI應用程序,AWT是圖形用戶界面編程的基礎,它的布局管理、事件機制、剪貼板操作等內(nèi)容仍然適用于Swing GUI編程.
第三篇:Java線程編程總結
線程編程方面
60、java中有幾種方法可以實現(xiàn)一個線程?用什么關鍵字修飾同步方法? stop()和suspend()方法為何不推薦使用?
答:有兩種實現(xiàn)方法,分別是繼承Thread類與實現(xiàn)Runnable接口 用synchronized關鍵字修飾同步方法
反對使用stop(),是因為它不安全。它會解除由線程獲取的所有鎖定,而且如果對象處于一種不連貫狀態(tài),那么其他線程能在那種狀態(tài)下檢查和修改它們。結果很難檢查出真正的問題所在。suspend()方法容易發(fā)生死鎖。調(diào)用suspend()的時候,目標線程會停下來,但卻仍然持有在這之前獲得的鎖定。此時,其他任何線程都不能訪問鎖定的資源,除非被“掛起”的線程恢復運行。對任何線程來說,如果它們想恢復目標線程,同時又試圖使用任何一個鎖定的資源,就會造成死鎖。所以不應該使用suspend(),而應在自己的Thread類中置入一個標志,指出線程應該活動還是掛起。若標志指出線程應該掛起,便用wait()命其進入等待狀態(tài)。若標志指出線程應當恢復,則用一個notify()重新啟動線程。61、sleep()和 wait()有什么區(qū)別? 答:sleep是線程類(Thread)的方法,導致此線程暫停執(zhí)行指定時間,給執(zhí)行機會給其他線程,但是監(jiān)控狀態(tài)依然保持,到時后會自動恢復。調(diào)用sleep不會釋放對象鎖。
wait是Object類的方法,對此對象調(diào)用wait方法導致本線程放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象發(fā)出notify方法(或notifyAll)后本線程才進入對象鎖定池準備獲得對象鎖進入運行狀態(tài)。
62、同步和異步有何異同,在什么情況下分別使用他們?舉例說明。
答:如果數(shù)據(jù)將在線程間共享。例如正在寫的數(shù)據(jù)以后可能被另一個線程讀到,或者正在讀的數(shù)據(jù)可能已經(jīng)被另一個線程寫過了,那么這些數(shù)據(jù)就是共享數(shù)據(jù),必須進行同步存取。
當應用程序在對象上調(diào)用了一個需要花費很長時間來執(zhí)行的方法,并且不希望讓程序等待方法的返回時,就應該使用異步編程,在很多情況下采用異步途徑往往更有效率。63、啟動一個線程是用run()還是start()? 答:啟動一個線程是調(diào)用start()方法,使線程所代表的虛擬處理機處于可運行狀態(tài),這意味著它可以由JVM調(diào)度并執(zhí)行。這并不意味著線程就會立即運行。run()方法可以產(chǎn)生必須退出的標志來停止一個線程。
64、當一個線程進入一個對象的一個synchronized方法后,其它線程是否可進入此對象的其它方法? 答:不能,一個對象的一個synchronized方法只能由一個線程訪問。
我認為:其他線程可以進入非synchronized方法,但不能進入這個對象的synchronized方法。65、請說出你所知道的線程同步的方法。
答:wait():使一個線程處于等待狀態(tài),并且釋放所持有的對象的lock。
sleep():使一個正在運行的線程處于睡眠狀態(tài),是一個靜態(tài)方法,調(diào)用此方法要捕捉InterruptedException異常。
notify():喚醒一個處于等待狀態(tài)的線程,注意的是在調(diào)用此方法的時候,并不能確切的喚醒某一個等待狀態(tài)的線程,而是由JVM確定喚醒哪個線程,而且不是按優(yōu)先級。
Allnotity():喚醒所有處入等待狀態(tài)的線程,注意并不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。
66、多線程有幾種實現(xiàn)方法,都是什么?同步有幾種實現(xiàn)方法,都是什么? 答:多線程有兩種實現(xiàn)方法,分別是繼承Thread類與實現(xiàn)Runnable接口 同步的實現(xiàn)方面有兩種,分別是synchronized,wait與notify 67、線程的基本概念、線程的基本狀態(tài)以及狀態(tài)之間的關系
答:線程指在程序執(zhí)行過程中,能夠執(zhí)行程序代碼的一個執(zhí)行單位,每個程序至少都有一個線程,也就是程序本身。
Java中的線程有四種狀態(tài)分別是:運行、就緒、掛起、結束
68、簡述synchronized和java.util.concurrent.locks.Lock的異同 ? 答:主要相同點:Lock能完成synchronized所實現(xiàn)的所有功能
主要不同點:Lock有比synchronized更精確的線程語義和更好的性能。synchronized會自動釋放鎖,而Lock一定要求程序員手工釋放,并且必須在finally從句中釋放。
Jsp方面
69、forward 和redirect的區(qū)別
答:forward是服務器請求資源,服務器直接訪問目標地址的URL,把那個URL的響應內(nèi)容讀取過來,然后把這些內(nèi)容再發(fā)給瀏覽器,瀏覽器根本不知道服務器發(fā)送的內(nèi)容是從哪兒來的,所以它的地址欄中還是原來的地址。
redirect就是服務端根據(jù)邏輯,發(fā)送一個狀態(tài)碼,告訴瀏覽器重新去請求那個地址,一般來說瀏覽器會用剛才請求的所有參數(shù)重新請求,所以session,request參數(shù)都可以獲取。70、jsp有哪些內(nèi)置對象?作用分別是什么?
答:JSP共有以下9種基本內(nèi)置組件(可與ASP的6種內(nèi)部組件相對應):
request 用戶端請求,此請求會包含來自GET/POST請求的參數(shù)
response 網(wǎng)頁傳回用戶端的回應
pageContext 網(wǎng)頁的屬性是在這里管理
session 與請求有關的會話期
application servlet 正在執(zhí)行的內(nèi)容
out 用來傳送回應的輸出 config servlet的構架部件
page JSP網(wǎng)頁本身
exception 針對錯誤網(wǎng)頁,未捕捉的例外
71、jsp有哪些動作?作用分別是什么? 答:JSP共有以下6種基本動作
jsp:include:在頁面被請求的時候引入一個文件。
jsp:useBean:尋找或者實例化一個JavaBean。
jsp:setProperty:設置JavaBean的屬性。
jsp:getProperty:輸出某個JavaBean的屬性。
jsp:forward:把請求轉到一個新的頁面。
jsp:plugin:根據(jù)瀏覽器類型為Java插件生成OBJECT或EMBED標記 72、JSP中動態(tài)INCLUDE與靜態(tài)INCLUDE的區(qū)別?
答:動態(tài)INCLUDE用jsp:include動作實現(xiàn)
靜態(tài)INCLUDE用include偽碼實現(xiàn),定不會檢查所含文件的變化,適用于包含靜態(tài)頁面
<%@ include file=“included.htm” %> 73、兩種跳轉方式分別是什么?有什么區(qū)別? 答:有兩種,分別為:
74、JSP的內(nèi)置對象及方法。
答:request表示HttpServletRequest對象。它包含了有關瀏覽器請求的信息,并且提供了幾個用于獲取cookie, header, 和session數(shù)據(jù)的有用的方法。
response表示HttpServletResponse對象,并提供了幾個用于設置送回 瀏覽器的響應的方法(如cookies,頭信息等)
out對象是javax.jsp.JspWriter的一個實例,并提供了幾個方法使你能用于向瀏覽器回送輸出結果。
pageContext表示一個javax.servlet.jsp.PageContext對象。它是用于方便存取各種范圍的名字空間、servlet相關的對象的API,并且包裝了通用的servlet相關功能的方法。
session表示一個請求的javax.servlet.http.HttpSession對象。Session可以存貯用戶的狀態(tài)信息
applicaton 表示一個javax.servle.ServletContext對象。這有助于查找有關servlet引擎和servlet環(huán)境的信息
config表示一個javax.servlet.ServletConfig對象。該對象用于存取servlet實例的初始化參數(shù)。page表示從該頁面產(chǎn)生的一個servlet實例
Servlet方面
75、說一說Servlet的生命周期?
答:servlet有良好的生存期的定義,包括加載和實例化、初始化、處理請求以及服務結束。這個生存期由javax.servlet.Servlet接口的init,service和destroy方法表達。Servlet被服務器實例化后,容器運行其init方法,請求到達時運行其service方法,service方法自動派遣運行與請求對應的doXXX方法(doGet,doPost)等,當服務器決定將實例銷毀的時候調(diào)用其destroy方法。
與cgi的區(qū)別在于servlet處于服務器進程中,它通過多線程方式運行其service方法,一個實例可以服務于多個請求,并且其實例一般不會銷毀,而CGI對每個請求都產(chǎn)生新的進程,服務完成后就銷毀,所以效率上低于servlet。
76、JAVA SERVLET API中forward()與redirect()的區(qū)別?
答:前者僅是容器中控制權的轉向,在客戶端瀏覽器地址欄中不會顯示出轉向后的地址;后者則是完全的跳轉,瀏覽器將會得到跳轉的地址,并重新發(fā)送請求鏈接。這樣,從瀏覽器的地址欄中可以看到跳轉后的鏈接地址。所以,前者更加高效,在前者可以滿足需要時,盡量使用forward()方法,并且,這樣也有助于隱藏實際的鏈接。在有些情況下,比如,需要跳轉到一個其它服務器上的資源,則必須使用sendRedirect()方法。77、Servlet的基本架構 答:
public class ServletName extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { } public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { } }
78、什么情況下調(diào)用doGet()和doPost()?
答:Jsp頁面中的form標簽里的method屬性為get時調(diào)用doGet(),為post時調(diào)用doPost()。79、servlet的生命周期
答:web容器加載servlet,生命周期開始。通過調(diào)用servlet的init()方法進行servlet的初始化。通過調(diào)用service()方法實現(xiàn),根據(jù)請求的不同調(diào)用不同的do***()方法。結束服務,web容器調(diào)用servlet的destroy()方法。
80、如何現(xiàn)實servlet的單線程模式 答:<%@ page isThreadSafe=“false”%> 81、頁面間對象傳遞的方法
答:request,session,application,cookie等
82、JSP和Servlet有哪些相同點和不同點,他們之間的聯(lián)系是什么?
答:JSP是Servlet技術的擴展,本質(zhì)上是Servlet的簡易方式,更強調(diào)應用的外表表達。JSP編譯后是“類servlet”。Servlet和JSP最主要的不同點在于,Servlet的應用邏輯是在Java文件中,并且完全從表示層中的HTML里分離開來。而JSP的情況是Java和HTML可以組合成一個擴展名為.jsp的文件。JSP側重于視圖,Servlet主要用于控制邏輯。83、四種會話跟蹤技術
答:會話作用域ServletsJSP 頁面描述
page否是代表與一個頁面相關的對象和屬性。一個頁面由一個編譯好的 Java servlet 類(可以帶有任何的 include 指令,但是沒有 include 動作)表示。這既包括 servlet 又包括被編譯成 servlet 的 JSP 頁面
request是是代表與 Web 客戶機發(fā)出的一個請求相關的對象和屬性。一個請求可能跨越多個頁面,涉及多個 Web 組件(由于 forward 指令和 include 動作的關系)
session是是代表與用于某個 Web 客戶機的一個用戶體驗相關的對象和屬性。一個 Web 會話可以也經(jīng)常會跨越多個客戶機請求
application是是代表與整個 Web 應用程序相關的對象和屬性。這實質(zhì)上是跨越整個 Web 應用程序,包括多個頁面、請求和會話的一個全局作用域 84、Request對象的主要方法 答:
setAttribute(String name,Object):設置名字為name的request的參數(shù)值 getAttribute(String name):返回由name指定的屬性值
getAttributeNames():返回request對象所有屬性的名字集合,結果是一個枚舉的實例 getCookies():返回客戶端的所有Cookie對象,結果是一個Cookie數(shù)組 getCharacterEncoding():返回請求中的字符編碼方式 getContentLength():返回請求的Body的長度
getHeader(String name):獲得HTTP協(xié)議定義的文件頭信息 getHeaders(String name):返回指定名字的request Header的所有值,結果是一個枚舉的實例 getHeaderNames():返回所以request Header的名字,結果是一個枚舉的實例 getInputStream():返回請求的輸入流,用于獲得請求中的數(shù)據(jù) getMethod():獲得客戶端向服務器端傳送數(shù)據(jù)的方法
getParameter(String name):獲得客戶端傳送給服務器端的有name指定的參數(shù)值
getParameterNames():獲得客戶端傳送給服務器端的所有參數(shù)的名字,結果是一個枚舉的實例 getParameterValues(String name):獲得有name指定的參數(shù)的所有值 getProtocol():獲取客戶端向服務器端傳送數(shù)據(jù)所依據(jù)的協(xié)議名稱 getQueryString():獲得查詢字符串
getRequestURI():獲取發(fā)出請求字符串的客戶端地址 getRemoteAddr():獲取客戶端的IP地址 getRemoteHost():獲取客戶端的名字
getSession([Boolean create]):返回和請求相關Session getServerName():獲取服務器的名字
getServletPath():獲取客戶端所請求的腳本文件的路徑 getServerPort():獲取服務器的端口號
removeAttribute(String name):刪除請求中的一個屬性
85、我們在web應用開發(fā)過程中經(jīng)常遇到輸出某種編碼的字符,如iso8859-1等,如何輸出一個某種編碼的字符串? 答:
Public String translate(String str){ String tempStr = “";try { tempStr = new String(str.getBytes(”ISO-8859-1“), ”GBK");tempStr = tempStr.trim();} catch(Exception e){ System.err.println(e.getMessage());} return tempStr;} 86、Servlet執(zhí)行時一般實現(xiàn)哪幾個方法? 答:
public void init(ServletConfig config)public ServletConfig getServletConfig()public String getServletInfo()public void service(ServletRequest request,ServletResponse response)public void destroy()
Jdbc、Jdo方面88、Jdo是什么?
87、Class.forName的作用?為什么要用?
答:調(diào)用該訪問返回一個以字符串指定類名的類的對象。答:JDO是Java對象持久化的新的規(guī)范,為java data object的簡稱,也是一個用于存取某種數(shù)據(jù)倉庫中的對象的標準化API。JDO提供了透明的對象存儲,因此對開發(fā)人員來說,存儲數(shù)據(jù)對象完全不需要額外的代碼(如JDBC API的使用)。這些繁瑣的例行工作已經(jīng)轉移到JDO產(chǎn)品提供商身上,使開發(fā)人員解脫出來,從而集中時間和精力在業(yè)務邏輯上。另外,JDO很靈活,因為它可以在任何數(shù)據(jù)底層上運行。JDBC只是面向關系數(shù)據(jù)庫(RDBMS)JDO更通用,提供到任何數(shù)據(jù)底層的存儲功能,比如關系數(shù)據(jù)庫、文件、XML以及對象數(shù)據(jù)庫(ODBMS)等等,使得應用可移植性更強。89、說出數(shù)據(jù)連接池的工作機制是什么? 答:J2EE服務器啟動時會建立一定數(shù)量的池連接,并一直維持不少于此數(shù)目的池連接。客戶端程序需要連接時,池驅動程序會返回一個未使用的池連接并將其表記為忙。如果當前沒有空閑連接,池驅動程序就新建一定數(shù)量的連接,新建連接的數(shù)量有配置參數(shù)決定。當使用的池連接調(diào)用完成后,池驅動程序將此連接表記為空閑,其他調(diào)用就可以使用這個連接。90、Jdo是什么? 答:JDO是Java對象持久化的新的規(guī)范,為java data object的簡稱,也是一個用于存取某種數(shù)據(jù)倉庫中的對象的標準化API。JDO提供了透明的對象存儲,因此對開發(fā)人員來說,存儲數(shù)據(jù)對象完全不需要額外的代碼(如JDBC API的使用)。這些繁瑣的例行工作已經(jīng)轉移到JDO產(chǎn)品提供商身上,使開發(fā)人員解脫出來,從而集中時間和精力在業(yè)務邏輯上。另外,JDO很靈活,因為它可以在任何數(shù)據(jù)底層上運行。JDBC只是面向關系數(shù)據(jù)庫(RDBMS)JDO更通用,提供到任何數(shù)據(jù)底層的存儲功能,比如關系數(shù)據(jù)庫、文件、XML以及對象數(shù)據(jù)庫(ODBMS)等等,使得應用可移植性更強。
Xml方面
91、xml有哪些解析技術?區(qū)別是什么? 答:有DOM,SAX,STAX等
DOM:處理大型文件時其性能下降的非常厲害。這個問題是由DOM的樹結構所造成的,這種結構占用的內(nèi)存較多,而且DOM必須在解析文件之前把整個文檔裝入內(nèi)存,適合對XML的隨機訪問。
SAX:不現(xiàn)于DOM,SAX是事件驅動型的XML解析方式。它順序讀取XML文件,不需要一次全部裝載整個文件。當遇到像文件開頭,文檔結束,或者標簽開頭與標簽結束時,它會觸發(fā)一個事件,用戶通過在其回調(diào)事件中寫入處理代碼來處理XML文件,適合對XML的順序訪問 STAX:Streaming API for XML(StAX)92、你在項目中用到了xml技術的哪些方面?如何實現(xiàn)的?
答:用到了數(shù)據(jù)存貯,信息配置兩方面。在做數(shù)據(jù)交換平臺時,將不能數(shù)據(jù)源的數(shù)據(jù)組裝成XML文件,然后將XML文件壓縮打包加密后通過網(wǎng)絡傳送給接收者,接收解密與解壓縮后再同XML文件中還原相關信息進行處理。在做軟件配置時,利用XML可以很方便的進行,軟件的各種配置參數(shù)都存貯在XML文件中。
93、XML文檔定義有幾種形式?它們之間有何本質(zhì)區(qū)別?解析XML文檔有哪幾種方式? 答:a: 兩種形式 dtd schema,b: 本質(zhì)區(qū)別:schema本身是xml的,可以被XML解析器解析(這也是從DTD上發(fā)展schema的根本目的),c:有DOM,SAX,STAX等
DOM:處理大型文件時其性能下降的非常厲害。這個問題是由DOM的樹結構所造成的,這種結構占用的內(nèi)存較多,而且DOM必須在解析文件之前把整個文檔裝入內(nèi)存,適合對XML的隨機訪問
SAX:不現(xiàn)于DOM,SAX是事件驅動型的XML解析方式。它順序讀取XML文件,不需要一次全部裝載整個文件。當遇到像文件開頭,文檔結束,或者標簽開頭與標簽結束時,它會觸發(fā)一個事件,用戶通過在其回調(diào)事件中寫入處理代碼來處理XML文件,適合對XML的順序訪問 STAX:Streaming API for XML(StAX)
第四篇:Java多線程編程總結
Java多線程編程總結
2007-05-17 11:21:59 標簽:java 多線程
原創(chuàng)作品,允許轉載,轉載時請務必以超鏈接形式標明文章 原始出處、作者信息和本聲明。否則將追究法律責任。http://lavasoft.blog.51cto.com/62575/27069
Java多線程編程總結
下面是Java線程系列博文的一個編目:
Java線程:概念與原理 Java線程:創(chuàng)建與啟動
Java線程:線程棧模型與線程的變量 Java線程:線程狀態(tài)的轉換 Java線程:線程的同步與鎖 Java線程:線程的交互 Java線程:線程的調(diào)度-休眠 Java線程:線程的調(diào)度-優(yōu)先級 Java線程:線程的調(diào)度-讓步 Java線程:線程的調(diào)度-合并 Java線程:線程的調(diào)度-守護線程 Java線程:線程的同步-同步方法 Java線程:線程的同步-同步塊
Java線程:并發(fā)協(xié)作-生產(chǎn)者消費者模型 Java線程:并發(fā)協(xié)作-死鎖 Java線程:volatile關鍵字 Java線程:新特征-線程池
Java線程:新特征-有返回值的線程 Java線程:新特征-鎖(上)Java線程:新特征-鎖(下)Java線程:新特征-信號量 Java線程:新特征-阻塞隊列 Java線程:新特征-阻塞棧 Java線程:新特征-條件變量 Java線程:新特征-原子量 Java線程:新特征-障礙器 Java線程:大總結
----
下面的內(nèi)容是很早之前寫的,內(nèi)容不夠充實,而且是基于Java1.4的內(nèi)容,Java5之后,線程并發(fā)部分擴展了相當多的內(nèi)容,因此建議大家看上面的系列文章的內(nèi)容,與時俱進,跟上Java發(fā)展的步伐。----
一、認識多任務、多進程、單線程、多線程 要認識多線程就要從操作系統(tǒng)的原理說起。
以前古老的DOS操作系統(tǒng)(V 6.22)是單任務的,還沒有線程的概念,系統(tǒng)在每次只能做一件事情。比如你在copy東西的時候不能rename文件名。為了提高系統(tǒng)的利用效率,采用批處理來批量執(zhí)行任務。
現(xiàn)在的操作系統(tǒng)都是多任務操作系統(tǒng),每個運行的任務就是操作系統(tǒng)所做的一件事情,比如你在聽歌的同時還在用MSN和好友聊天。聽歌和聊天就是兩個任務,這個兩個任務是“同時”進行的。一個任務一般對應一個進程,也可能包含好幾個進程。比如運行的MSN就對應一個MSN的進程,如果你用的是windows系統(tǒng),你就可以在任務管理器中看到操作系統(tǒng)正在運行的進程信息。
一般來說,當運行一個應用程序的時候,就啟動了一個進程,當然有些會啟動多個進程。啟動進程的時候,操作系統(tǒng)會為進程分配資源,其中最主要的資源是內(nèi)存空間,因為程序是在內(nèi)存中運行的。在進程中,有些程序流程塊是可以亂序執(zhí)行的,并且這個代碼塊可以同時被多次執(zhí)行。實際上,這樣的代碼塊就是線程體。線程是進程中亂序執(zhí)行的代碼流程。當多個線程同時運行的時候,這樣的執(zhí)行模式成為并發(fā)執(zhí)行。
多線程的目的是為了最大限度的利用CPU資源。
Java編寫程序都運行在在Java虛擬機(JVM)中,在JVM的內(nèi)部,程序的多任務是通過線程來實現(xiàn)的。每用java命令啟動一個java應用程序,就會啟動一個JVM進程。在同一個JVM進程中,有且只有一個進程,就是它自己。在這個JVM環(huán)境中,所有程序代碼的運行都是以線程來運行。
一般常見的Java應用程序都是單線程的。比如,用java命令運行一個最簡單的HelloWorld的Java應用程序時,就啟動了一個JVM進程,JVM找到程序程序的入口點main(),然后運行main()方法,這樣就產(chǎn)生了一個線程,這個線程稱之為主線程。當main方法結束后,主線程運行完成。JVM進程也隨即退出。
對于一個進程中的多個線程來說,多個線程共享進程的內(nèi)存塊,當有新的線程產(chǎn)生的時候,操作系統(tǒng)不分配新的內(nèi)存,而是讓新線程共享原有的進程塊的內(nèi)存。因此,線程間的通信很容易,速度也很快。不同的進程因為處于不同的內(nèi)存塊,因此進程之間的通信相對困難。
實際上,操作的系統(tǒng)的多進程實現(xiàn)了多任務并發(fā)執(zhí)行,程序的多線程實現(xiàn)了進程的并發(fā)執(zhí)行。多任務、多進程、多線程的前提都是要求操作系統(tǒng)提供多任務、多進程、多線程的支持。
在Java程序中,JVM負責線程的調(diào)度。線程調(diào)度是值按照特定的機制為多個線程分配CPU的使用權。調(diào)度的模式有兩種:分時調(diào)度和搶占式調(diào)度。分時調(diào)度是所有線程輪流獲得CPU使用權,并平均分配每個線程占用CPU的時間;搶占式調(diào)度是根據(jù)線程的優(yōu)先級別來獲取CPU的使用權。JVM的線程調(diào)度模式采用了搶占式模式。
所謂的“并發(fā)執(zhí)行”、“同時”其實都不是真正意義上的“同時”。眾所周知,CPU都有個時鐘頻率,表示每秒中能執(zhí)行cpu指令的次數(shù)。在每個時鐘周期內(nèi),CPU實際上只能去執(zhí)行一條(也有可能多條)指令。操作系統(tǒng)將進程線程進行管理,輪流(沒有固定的順序)分配每個進程很短的一段是時間(不一定是均分),然后在每個線程內(nèi)部,程序代碼自己處理該進程內(nèi)部線程的時間分配,多個線程之間相互的切換去執(zhí)行,這個切換時間也是非常短的。因此多任務、多進程、多線程都是操作系統(tǒng)給人的一種宏觀感受,從微觀角度看,程序的運行是異步執(zhí)行的。
用一句話做總結:雖然操作系統(tǒng)是多線程的,但CPU每一時刻只能做一件事,和人的大腦是一樣的,呵呵。
二、Java與多線程
Java語言的多線程需要操作系統(tǒng)的支持。
Java 虛擬機允許應用程序并發(fā)地運行多個執(zhí)行線程。Java語言提供了多線程編程的擴展點,并給出了功能強大的線程控制API。
在Java中,多線程的實現(xiàn)有兩種方式: 擴展java.lang.Thread類 實現(xiàn)java.lang.Runnable接口
每個線程都有一個優(yōu)先級,高優(yōu)先級線程的執(zhí)行優(yōu)先于低優(yōu)先級線程。每個線程都可以或不可以標記為一個守護程序。當某個線程中運行的代碼創(chuàng)建一個新 Thread 對象時,該新線程的初始優(yōu)先級被設定為創(chuàng)建線程的優(yōu)先級,并且當且僅當創(chuàng)建線程是守護線程時,新線程才是守護程序。
當 Java 虛擬機啟動時,通常都會有單個非守護線程(它通常會調(diào)用某個指定類的 main 方法)。Java 虛擬機會繼續(xù)執(zhí)行線程,直到下列任一情況出現(xiàn)時為止:
調(diào)用了 Runtime 類的 exit 方法,并且安全管理器允許退出操作發(fā)生。
非守護線程的所有線程都已停止運行,無論是通過從對 run 方法的調(diào)用中返回,還是通過拋出一個傳播到 run 方法之外的異常。
三、擴展java.lang.Thread類
/** * File Name: TestMitiThread.java * Created by: IntelliJ IDEA.* Copyright: Copyright(c)2003-2006 * Company: Lavasoft([url]http://lavasoft.blog.51cto.com/[/url])* Author: leizhimin * Modifier: leizhimin * Date Time: 2007-5-17 10:03:12 * Readme: 通過擴展Thread類實現(xiàn)多線程 */ public class TestMitiThread { public static void main(String[] rags){ System.out.println(Thread.currentThread().getName()+ “ 線程運行開始!”);new MitiSay(“A”).start();new MitiSay(“B”).start();System.out.println(Thread.currentThread().getName()+ “ 線程運行結束!”);} }
class MitiSay extends Thread { public MitiSay(String threadName){ super(threadName);}
public void run(){ System.out.println(getName()+ “ 線程運行開始!”);for(int i = 0;i < 10;i++){ System.out.println(i + “ ” + getName());try { sleep((int)Math.random()* 10);} catch(InterruptedException e){ e.printStackTrace();} } System.out.println(getName()+ “ 線程運行結束!”);} }
運行結果:
main 線程運行開始!main 線程運行結束!A 線程運行開始!0 A 1 A B 線程運行開始!2 A 0 B 3 A 4 A 1 B 5 A 6 A 7 A 8 A 9 A A 線程運行結束!2 B 3 B 4 B 5 B 6 B 7 B 8 B 9 B B 線程運行結束!說明:
程序啟動運行main時候,java虛擬機啟動一個進程,主線程main在main()調(diào)用時候被創(chuàng)建。隨著調(diào)用MitiSay的兩個對象的start方法,另外兩個線程也啟動了,這樣,整個應用就在多線程下運行。
在一個方法中調(diào)用Thread.currentThread().getName()方法,可以獲取當前線程的名字。在mian方法中調(diào)用該方法,獲取的是主線程的名字。
注意:start()方法的調(diào)用后并不是立即執(zhí)行多線程代碼,而是使得該線程變?yōu)榭蛇\行態(tài)(Runnable),什么時候運行是由操作系統(tǒng)決定的。
從程序運行的結果可以發(fā)現(xiàn),多線程程序是亂序執(zhí)行。因此,只有亂序執(zhí)行的代碼才有必要設計為多線程。
Thread.sleep()方法調(diào)用目的是不讓當前線程獨自霸占該進程所獲取的CPU資源,以留出一定時間給其他線程執(zhí)行的機會。
實際上所有的多線程代碼執(zhí)行順序都是不確定的,每次執(zhí)行的結果都是隨機的。
四、實現(xiàn)java.lang.Runnable接口
/** * 通過實現(xiàn) Runnable 接口實現(xiàn)多線程 */ public class TestMitiThread1 implements Runnable {
public static void main(String[] args){ System.out.println(Thread.currentThread().getName()+ “ 線程運行開始!”);TestMitiThread1 test = new TestMitiThread1();Thread thread1 = new Thread(test);Thread thread2 = new Thread(test);thread1.start();thread2.start();System.out.println(Thread.currentThread().getName()+ “ 線程運行結束!”);}
public void run(){ System.out.println(Thread.currentThread().getName()+ “ 線程運行開始!”);for(int i = 0;i < 10;i++){ System.out.println(i + “ ” + Thread.currentThread().getName());try { Thread.sleep((int)Math.random()* 10);} catch(InterruptedException e){ e.printStackTrace();} } System.out.println(Thread.currentThread().getName()+ “ 線程運行結束!”);} }
運行結果:
main 線程運行開始!Thread-0 線程運行開始!main 線程運行結束!0 Thread-0 Thread-1 線程運行開始!0 Thread-1 1 Thread-1 1 Thread-0 2 Thread-0 2 Thread-1 3 Thread-0 3 Thread-1 4 Thread-0 4 Thread-1 5 Thread-0 6 Thread-0 5 Thread-1 7 Thread-0 8 Thread-0 6 Thread-1 9 Thread-0 7 Thread-1 Thread-0 線程運行結束!8 Thread-1 9 Thread-1 Thread-1 線程運行結束!說明:
TestMitiThread1類通過實現(xiàn)Runnable接口,使得該類有了多線程類的特征。run()方法是多線程程序的一個約定。所有的多線程代碼都在run方法里面。Thread類實際上也是實現(xiàn)了Runnable接口的類。
在啟動的多線程的時候,需要先通過Thread類的構造方法Thread(Runnable target)構造出對象,然后調(diào)用Thread對象的start()方法來運行多線程代碼。
實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。因此,不管是擴展Thread類還是實現(xiàn)Runnable接口來實現(xiàn)多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。
五、讀解Thread類API
static int MAX_PRIORITY 線程可以具有的最高優(yōu)先級。static int MIN_PRIORITY 線程可以具有的最低優(yōu)先級。static int NORM_PRIORITY 分配給線程的默認優(yōu)先級。
構造方法摘要
Thread(Runnable target)分配新的 Thread 對象。Thread(String name)分配新的 Thread 對象。
方法摘要
static Thread currentThread()返回對當前正在執(zhí)行的線程對象的引用。ClassLoader getContextClassLoader()返回該線程的上下文 ClassLoader。long getId()返回該線程的標識符。String getName()返回該線程的名稱。int getPriority()返回線程的優(yōu)先級。Thread.State getState()返回該線程的狀態(tài)。ThreadGroup getThreadGroup()返回該線程所屬的線程組。static boolean holdsLock(Object obj)當且僅當當前線程在指定的對象上保持監(jiān)視器鎖時,才返回 true。void interrupt()中斷線程。
static boolean interrupted()測試當前線程是否已經(jīng)中斷。boolean isAlive()測試線程是否處于活動狀態(tài)。boolean isDaemon()測試該線程是否為守護線程。boolean isInterrupted()測試線程是否已經(jīng)中斷。void join()等待該線程終止。void join(long millis)等待該線程終止的時間最長為 millis 毫秒。void join(long millis, int nanos)等待該線程終止的時間最長為 millis 毫秒 + nanos 納秒。void resume()已過時。該方法只與 suspend()一起使用,但 suspend()已經(jīng)遭到反對,因為它具有死鎖傾向。有關更多信息,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。void run()如果該線程是使用獨立的 Runnable 運行對象構造的,則調(diào)用該 Runnable 對象的 run 方法;否則,該方法不執(zhí)行任何操作并返回。void setContextClassLoader(ClassLoader cl)設置該線程的上下文 ClassLoader。void setDaemon(boolean on)將該線程標記為守護線程或用戶線程。
static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)設置當線程由于未捕獲到異常而突然終止,并且沒有為該線程定義其他處理程序時所調(diào)用的默認處理程序。void setName(String name)改變線程名稱,使之與參數(shù) name 相同。void setPriority(int newPriority)更改線程的優(yōu)先級。
void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)設置該線程由于未捕獲到異常而突然終止時調(diào)用的處理程序。static void sleep(long millis)在指定的毫秒數(shù)內(nèi)讓當前正在執(zhí)行的線程休眠(暫停執(zhí)行)。static void sleep(long millis, int nanos)在指定的毫秒數(shù)加指定的納秒數(shù)內(nèi)讓當前正在執(zhí)行的線程休眠(暫停執(zhí)行)。void start()使該線程開始執(zhí)行;Java 虛擬機調(diào)用該線程的 run 方法。void stop()已過時。該方法具有固有的不安全性。用 Thread.stop 來終止線程將釋放它已經(jīng)鎖定的所有監(jiān)視器(作為沿堆棧向上傳播的未檢查 ThreadDeath 異常的一個自然后果)。如果以前受這些監(jiān)視器保護的任何對象都處于一種不一致的狀態(tài),則損壞的對象將對其他線程可見,這有可能導致任意的行為。stop 的許多使用都應由只修改某些變量以指示目標線程應該停止運行的代碼來取代。目標線程應定期檢查該變量,并且如果該變量指示它要停止運行,則從其運行方法依次返回。如果目標線程等待很長時間(例如基于一個條件變量),則應使用 interrupt 方法來中斷該等待。有關更多信息,請參閱《為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?》。void stop(Throwable obj)已過時。該方法具有固有的不安全性。請參閱 stop()以獲得詳細信息。該方法的附加危險是它可用于生成目標線程未準備處理的異常(包括若沒有該方法該線程不太可能拋出的已檢查的異常)。有關更多信息,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。void suspend()已過時。該方法已經(jīng)遭到反對,因為它具有固有的死鎖傾向。如果目標線程掛起時在保護關鍵系統(tǒng)資源的監(jiān)視器上保持有鎖,則在目標線程重新開始以前任何線程都不能訪問該資源。如果重新開始目標線程的線程想在調(diào)用 resume 之前鎖定該監(jiān)視器,則會發(fā)生死鎖。這類死鎖通常會證明自己是“凍結”的進程。有關更多信息,請參閱為何 Thread.stop、Thread.suspend 和 Thread.resume 遭到反對?。String toString()返回該線程的字符串表示形式,包括線程名稱、優(yōu)先級和線程組。static void yield()暫停當前正在執(zhí)行的線程對象,并執(zhí)行其他線程。
六、線程的狀態(tài)轉換圖
線程在一定條件下,狀態(tài)會發(fā)生變化。線程變化的狀態(tài)轉換圖如下:
1、新建狀態(tài)(New):新創(chuàng)建了一個線程對象。
2、就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運行線程池中,變得可運行,等待獲取CPU的使用權。
3、運行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。
4、阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態(tài),才有機會轉到運行狀態(tài)。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執(zhí)行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態(tài)。
5、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結束生命周期。
七、線程的調(diào)度
1、調(diào)整線程優(yōu)先級:Java線程有優(yōu)先級,優(yōu)先級高的線程會獲得較多的運行機會。
Java線程的優(yōu)先級用整數(shù)表示,取值范圍是1~10,Thread類有以下三個靜態(tài)常量: static int MAX_PRIORITY 線程可以具有的最高優(yōu)先級,取值為10。static int MIN_PRIORITY 線程可以具有的最低優(yōu)先級,取值為1。static int NORM_PRIORITY 分配給線程的默認優(yōu)先級,取值為5。
Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優(yōu)先級。
每個線程都有默認的優(yōu)先級。主線程的默認優(yōu)先級為Thread.NORM_PRIORITY。
線程的優(yōu)先級有繼承關系,比如A線程中創(chuàng)建了B線程,那么B將和A具有相同的優(yōu)先級。JVM提供了10個線程優(yōu)先級,但與常見的操作系統(tǒng)都不能很好的映射。如果希望程序能移植到各個操作系統(tǒng)中,應該僅僅使用Thread類有以下三個靜態(tài)常量作為優(yōu)先級,這樣能保證同樣的優(yōu)先級采用了同樣的調(diào)度方式。
2、線程睡眠:Thread.sleep(long millis)方法,使線程轉到阻塞狀態(tài)。millis參數(shù)設定睡眠的時間,以毫秒為單位。當睡眠結束后,就轉為就緒(Runnable)狀態(tài)。sleep()平臺移植性好。
3、線程等待:Object類中的wait()方法,導致當前的線程等待,直到其他線程調(diào)用此對象的 notify()方法或 notifyAll()喚醒方法。這個兩個喚醒方法也是Object類中的方法,行為等價于調(diào)用 wait(0)一樣。
4、線程讓步:Thread.yield()方法,暫停當前正在執(zhí)行的線程對象,把執(zhí)行機會讓給相同或者更高優(yōu)先級的線程。
5、線程加入:join()方法,等待其他線程終止。在當前線程中調(diào)用另一個線程的join()方法,則當前線程轉入阻塞狀態(tài),直到另一個進程運行結束,當前線程再由阻塞轉為就緒狀態(tài)。
6、線程喚醒:Object類中的notify()方法,喚醒在此對象監(jiān)視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,并在對實現(xiàn)做出決定時發(fā)生。線程通過調(diào)用其中一個 wait 方法,在對象的監(jiān)視器上等待。直到當前的線程放棄此對象上的鎖定,才能繼續(xù)執(zhí)行被喚醒的線程。被喚醒的線程將以常規(guī)方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。類似的方法還有一個notifyAll(),喚醒在此對象監(jiān)視器上等待的所有線程。注意:Thread中suspend()和resume()兩個方法在JDK1.5中已經(jīng)廢除,不再介紹。因為有死鎖傾向。
7、常見線程名詞解釋
主線程:JVM調(diào)用程序mian()所產(chǎn)生的線程。
當前線程:這個是容易混淆的概念。一般指通過Thread.currentThread()來獲取的進程。后臺線程:指為其他線程提供服務的線程,也稱為守護線程。JVM的垃圾回收線程就是一個后臺線程。
前臺線程:是指接受后臺線程服務的線程,其實前臺后臺線程是聯(lián)系在一起,就像傀儡和幕后操縱者一樣的關系。傀儡是前臺線程、幕后操縱者是后臺線程。由前臺線程創(chuàng)建的線程默認也是前臺線程。可以通過isDaemon()和setDaemon()方法來判斷和設置一個線程是否為后臺線程。
本文出自 “熔 巖” 博客,請務必保留此出處http://lavasoft.blog.51cto.com/62575/27069
第五篇:Java AWT編程總結
1.什么是GUI? a)GUI是Graphics User Interface的全稱,意思是圖形用戶界面.2.為什么需要GUI? a)圖形用戶界面能夠讓最終用戶通過鼠標拖動、單擊等動作就可以操作整個應用,從而提高應用的用戶體驗效果,使程序受到用戶的歡迎.3.Java通過AWT和SWING來完成GUI圖形用戶界面編程.4.AWT a)AWT是SUN公司提供的一個基本的GUI類庫,被稱為抽象工具集(Abstract Window-Toolkit),它為Java應用程序提供了基本的組件.b)AWT組件需要調(diào)用運行平臺的圖形界面來創(chuàng)建和平臺一致的對等體,所以AWT只能使用所有平臺都支持的公共組件,因此AWT只能夠提供一些 常用的GUI組件.5.AWT的主要組成部分
a)Component,代表一個具體圖形表示能力的對象,可以在屏幕上顯示,并與用戶交互.通常我們把它稱為”組件”.b)MenuComponent,代表圖形界面的菜單.i.MenuBar,代表菜單條.ii.Menu,代表一個菜單項的集合.iii.MenuItem,代表一個菜單項.c)Container,代表一個AWT組件容器,可以盛裝其他Commponent組件,它繼承自Component抽象類,本身也代表一個Component組件.i.Window,可獨立存在的頂級窗口.1.Frame,代表一個窗體.2.Dialog,代表一個對話框
a)FileDialog代表一個文件對話框,用于打開或保存文件.Panel,可容納其他組件,但不能獨立存在,必須被添加到其他容器中.ii.iii.ScrollPane,帶滾動條的容器.d)LayoutManager,布局管理器,表示容器管理其他組件的方式.i.ii.iii.iv.v.vi.FlowLayout,流式布局,類似于Window平臺記事本的文本布局方式.BorderLayout,邊框布局,只能盛裝5個組件,這5個組件分別位于邊框布局容器的東西南北中五個方位.GridLayout,網(wǎng)格布局,將組件以網(wǎng)格形式顯示在容器中.GridBagLayout,網(wǎng)格包布局,一種較為復雜的布局管理器,依賴GridBagConstraints來約束組件.CardLayout,卡片布局,以時間來管理容器內(nèi)的組件,將組件看作是一張張卡片,每次顯示最外面一張卡片(組件).BoxLayou,箱式布局,通常與Box容器結合使用.6.AWT 的事件
a)應用程序響應用戶的某個動作或請求,如用戶單擊了一下鼠標,用戶請求關閉應用程序窗口等.b)AWT編程中,所有事件的處理都必須交給特定的對象來完成,我們將這個特定的對象稱為事件監(jiān)聽器.c)AWT的事件處理機制是一種委派式的事件處理方式,通過將某個事件監(jiān)聽器注冊到用戶指定的組件,當用戶進行某個操作并觸發(fā)指定事件時,應用程序會自動產(chǎn)生一個事件(Event)對象并作為參數(shù)傳給事件監(jiān)聽器中的事件處理器,然后由事件監(jiān)聽器通知事件處理器來響應用戶,完成用戶的請求.d)不同的事件需要不同的事件監(jiān)聽器,不同的監(jiān)聽器需要實現(xiàn)不同的監(jiān)聽器接口.e)事件監(jiān)聽器接口:為某個特定事件定義了響應用戶請求的方法,當用戶將某個事件監(jiān)聽器注冊到指定組件上以響應特定的事件時,則該事件監(jiān)聽器必須實現(xiàn)對應的事件監(jiān)聽器接口才能對用戶的請求進行有效處理.例如,用戶點擊了鼠標右鍵,希望打開某個應用程序的右鍵菜單,則注冊到該應用程序上的事件監(jiān)聽器必須實現(xiàn)鼠標事件監(jiān)聽器接口,并實現(xiàn)該接口內(nèi)部某些方法來完成用戶的請求.f)事件適配器,很多時候,我們只需要實現(xiàn)某個事件監(jiān)聽器接口中個別方法就能完成應用程序的實際需求,但實現(xiàn)該事件監(jiān)聽器接口的類必須實現(xiàn)該接口中所有的抽象方法,這會造成代碼的冗余.而事件適配器可以幫我們解決這個問題,事件適配器實現(xiàn)了所有的擁有多個抽象方法的事件監(jiān)聽器接口,并空實現(xiàn)了這些接口中所有的抽象方法,所謂空實現(xiàn),就是方法中沒有任何實現(xiàn)代碼,因此,我們可以通過繼承對應事件監(jiān)聽器接口的事件適配器抽象類,并實現(xiàn)我們感興趣的方法來完成應用需求即可.g)Java事件處理過程中主要涉及的三類對象 i.事件源,通常為普通組件.ii.事件,通常指用戶的某個操作,如單擊了一下鼠標,按了一下回車鍵.iii.事件監(jiān)聽器,負責監(jiān)聽事件源上所發(fā)生的事件,并作出響應.h)AWT事件監(jiān)聽器的實現(xiàn)形式 i.ii.內(nèi)部類形式 頂級類形式
iii.類本身作為事件監(jiān)聽器 iv.匿名內(nèi)部類形式 v.注:目前最為流行的事件監(jiān)聽器的實現(xiàn)形式是內(nèi)部類形式和匿名內(nèi)部類形式.7.AWT繪圖
a)AWT繪圖的實現(xiàn)過程.i.重寫畫布類的paint方法,繪圖圖形.ii.注冊事件監(jiān)聽器到指定的組件.iii.調(diào)用Component類的repaint方法繪制圖形.b)AWT實現(xiàn)繪圖主要涉及的對象 i.ii.c)Component類的子類Canvas類,它代表一個畫布.Graphics,代表一個畫筆,可以在Canvas的子類中繪制用戶自訂的圖形.Image類代表了位圖,它的一個主要的實現(xiàn)類BufferedImage是可以訪問圖形數(shù)據(jù)緩沖區(qū),并可以返回一個Graphics對象來繪制該BuuferedImage.d)可以使用ImageIO工具類的ImageReader和ImageWriter讀寫磁盤上的位圖文件.8.AWT的優(yōu)缺點
a)AWT在許多非桌面環(huán)境,如嵌入式設備中有著自己的優(yōu)勢,它的主要優(yōu)點如下:
i.ii.iii.iv.更少的內(nèi)存:對運行在有限環(huán)境中的GUI程序的開發(fā),是合適的。
2.更少的啟動事件:由于AWT組件是本地由操作系統(tǒng)實現(xiàn)的。絕大多數(shù)的二進制代碼已經(jīng)在如系統(tǒng)啟動的時候被預裝載了,這降低了它的啟動事件。3.更好的響應:由于本地組件由操作系統(tǒng)渲染。4.成熟穩(wěn)定的:能夠正常工作并很少使你的程序崩潰。
b)同樣它也有不少的缺點 i.ii.iii.更少組件類型:表和樹這些重要的組件缺失了。它們是桌面應用程序中普遍使用的。
2.缺乏豐富的組件特征:按鈕不支持圖片。
3.無擴展性:AWT的組件是本地組件。JVM中的AWT類實例實際只是包含本地組件的引用。唯一的擴展點是AWT的Canvas組件,可以從零開始創(chuàng)建自定義組件。然而無法繼承和重用一個已有的AWT組件
9.AWT總結:AWT是SUN不推薦使用的工具集,實際開發(fā)中很少使用AWT而是使用SUN公司和Netscape公司共同開發(fā)的一個新的用戶界面庫-Swing來開發(fā)GUI應用程序,AWT是圖形用戶界面編程的基礎,它的布局管理、事件機制、剪貼板操作等內(nèi)容仍然適用于Swing GUI編程.