鄭逸凡
(福州外語外貿學院,福建 福州 350202)
在操作系統中,進程是程序的一次執行,比如當雙擊某個可執行文件后,系統就創建一個進程專門執行這個程序的代碼,在執行過程中,進程會申請、持有或釋放操作系統資源(文件、內存等).在操作系統發展早期,進程是資源分配、調度、執行的基本單位,但由于進程持有系統資源等,調度時系統開銷很大,于是便出現了輕量級進程——線程.
一個進程可擁有多個線程,這些線程共享此進程所持有的系統資源.現代操作系統中,調度、執行的基本單位變成了線程,進程則還是資源分配的基本單位.由于線程本身幾乎不持有系統資源,在調度時系統開銷就很小.操作系統可以擁有多個進程,感覺就像多個程序同時在執行;進程可以擁有多個線程,感覺就像一個程序可以同時做多件事情.
多線程編程是指讓程序使用多個線程同時分別做一件事情的不同部分,或者同時做不同的事情,但并不是所有的事情都適合多線程,多線程編程的目的是提高程序執行效率、提高人們的工作效率.
在Java中,Thread類是所有線程類的超類,開發人員可以編寫一個類繼承Thread,并重寫run方法,在run方法里面編寫線程將要執行的代碼.創建線程對象后,只需要調用start()方法即可讓線程進入就緒隊列,等待操作系統調度.需要特別注意的是調度具有隨機性和隨時性,也就是說無法確定下一次調度哪個線程,也無法確定什么時刻進行調度.在Java中,繼承Thread類創建線程的代碼如下:
public class ThreadTest{
public static void main(String[]args){
MyThread myThread=new MyThread();
myThread.start();
}
}
class MyThread extends Thread{
@Override
public void run(){
System.out.println("自己創建的線程執行了");
}
}
除了繼承Thread類重寫run方法外,在簡單的情況下,還可通過實現Runnable接口的方式編寫線程執行的代碼,具體實現代碼如下:
Thread thread=new Thread(new Runnable(){
@Override
public void run(){
System.out.println("Runnable接口方式實現多線程");
}
});
一個數據,如一個對象或對象中的某個字段,如果有多個線程可以同時訪問它,就可能會出現線程安全問題:數據錯亂、程序出錯或其他無法預知的問題.比如線程1要遍歷一個list集合,線程2要把這個list集合清空,如果這兩個線程同時執行就可能會出現線程安全問題.線程同步控制,即使用某種方式使得一個線程在操作完某個數據前,別的線程無法操作這個數據,從而避免多個線程同時操作一個數據,進而避免線程安全問題.
在Java中每個對象都有一把鎖,同一時刻只能有一個線程持有這把鎖,線程可以使用synchronized關鍵字向系統申請某個對象的鎖,得到鎖之后,別的線程再申請該鎖時,就只能等待.持有鎖的線程在這次操作完成后,可以釋放鎖,以便其他線程可以獲得鎖.例如,以synchronized代碼塊實現同步鎖機制的主要代碼如下:
Thread thread1=new Thread(new Runnable(){
@Override
public void run(){
synchronized(list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
}
});
Thread thread2=new Thread(new Runnable(){
@Override
public void run(){
synchronized(list){
list.clear();
}
}
});
對于稍復雜的情況,比如多個線程需要相互合作有規律的訪問共享數據,就可以使用wait/notify機制,即等待/通知機制,也稱等待/喚醒機制.
等待/通知機制建立在synchronized同步鎖機制的基礎上,即在同步代碼塊(或同步方法)內,如果當前線程執行了lockObject.wait()(lockObject表示提供鎖的對象),則當前線程立即暫停執行,并被放入阻塞隊列,并向系統歸還所持有的鎖,并在lockObject上等待,直到別的線程調用lockObject.notify().如果有多個線程在同一個對象上等待,notify()方法只會隨機通知一個等待的線程,也可以使用notifyAll()方法通知所有等待的線程.被通知的線程獲得鎖后會進入就緒隊列.
假設線程1需要同時擁有資源A和資源B才能工作,線程2需要同時擁有資源A和資源B才能工作,在進行同步控制時有可能出現這種情況:線程1擁有資源A,線程2擁有資源B,兩個線程相互等待對方先釋放資源,并會一直這么僵持下去,這種情況稱為死鎖.
為了避免死鎖,可以使用信號量機制:線程在嘗試申請某個資源前都要判斷能否一次性就獲得所有需要的資源,如果能,就申請,如果不能,則不申請,一直等到可以一次性獲得所有資源.
網絡編程,主要是指基于TCP的網絡通信編程,在Java中網絡編程是使用Socket類實現,因此也稱為socket編程.socket編程模型中有服務器端和客戶端,服務器端使用ServerSocket創建,一般有固定的IP地址和端口號,方便向外界提供服務.客戶端可以有多個,并且使用Socket主動連接服務器.連接后,服務器端也創建一個Socket對象表示這次連接.
在Java中實現socket編程,服務器端要做的事情主要有:創建服務器對象ServerSocket;等待客戶端的連接請求,收到請求后即返回表示這次連接的Socket對象;開啟新的線程專門處理這個連接;獲得連接的輸入輸出流,并按照一定的規則進行數據交換;關閉連接(關閉連接時會自動關閉IO流).服務器端socket編程的主要代碼如下:
public class ServerTest{
public static void main(String[]args){
try{
ServerSocket server=new ServerSocket(10002);
while(true){
Socket socket=server.accept();
MyThread myThread=new MyThread(socket);
myThread.start();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
在Java中實現socket編程,客戶端要做的事情主要有:創建Socket對象,即向服務器申請連接;獲得連接的輸入輸出流,并按照一定的規則進行數據交換;最后關閉連接(關閉連接時會自動關閉IO流).客戶端socket編程的主要代碼如下:
public class ClientTest{
public static void main(String[]args)throws IOException{
Socket socket=new Socket("localhost",10001);
InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream();
byte[]buff=new byte[1024];
int len=inputStream.read(buff);
System.out.println(new String(buff,0,len));
socket.close();
}
}