鄭逸凡
(福州外語(yǔ)外貿(mào)學(xué)院,福建 福州 350202)
在操作系統(tǒng)中,進(jìn)程是程序的一次執(zhí)行,比如當(dāng)雙擊某個(gè)可執(zhí)行文件后,系統(tǒng)就創(chuàng)建一個(gè)進(jìn)程專(zhuān)門(mén)執(zhí)行這個(gè)程序的代碼,在執(zhí)行過(guò)程中,進(jìn)程會(huì)申請(qǐng)、持有或釋放操作系統(tǒng)資源(文件、內(nèi)存等).在操作系統(tǒng)發(fā)展早期,進(jìn)程是資源分配、調(diào)度、執(zhí)行的基本單位,但由于進(jìn)程持有系統(tǒng)資源等,調(diào)度時(shí)系統(tǒng)開(kāi)銷(xiāo)很大,于是便出現(xiàn)了輕量級(jí)進(jìn)程——線程.
一個(gè)進(jìn)程可擁有多個(gè)線程,這些線程共享此進(jìn)程所持有的系統(tǒng)資源.現(xiàn)代操作系統(tǒng)中,調(diào)度、執(zhí)行的基本單位變成了線程,進(jìn)程則還是資源分配的基本單位.由于線程本身幾乎不持有系統(tǒng)資源,在調(diào)度時(shí)系統(tǒng)開(kāi)銷(xiāo)就很小.操作系統(tǒng)可以擁有多個(gè)進(jìn)程,感覺(jué)就像多個(gè)程序同時(shí)在執(zhí)行;進(jìn)程可以擁有多個(gè)線程,感覺(jué)就像一個(gè)程序可以同時(shí)做多件事情.
多線程編程是指讓程序使用多個(gè)線程同時(shí)分別做一件事情的不同部分,或者同時(shí)做不同的事情,但并不是所有的事情都適合多線程,多線程編程的目的是提高程序執(zhí)行效率、提高人們的工作效率.
在Java中,Thread類(lèi)是所有線程類(lèi)的超類(lèi),開(kāi)發(fā)人員可以編寫(xiě)一個(gè)類(lèi)繼承Thread,并重寫(xiě)run方法,在run方法里面編寫(xiě)線程將要執(zhí)行的代碼.創(chuàng)建線程對(duì)象后,只需要調(diào)用start()方法即可讓線程進(jìn)入就緒隊(duì)列,等待操作系統(tǒng)調(diào)度.需要特別注意的是調(diào)度具有隨機(jī)性和隨時(shí)性,也就是說(shuō)無(wú)法確定下一次調(diào)度哪個(gè)線程,也無(wú)法確定什么時(shí)刻進(jìn)行調(diào)度.在Java中,繼承Thread類(lèi)創(chuàng)建線程的代碼如下:
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("自己創(chuàng)建的線程執(zhí)行了");
}
}
除了繼承Thread類(lèi)重寫(xiě)run方法外,在簡(jiǎn)單的情況下,還可通過(guò)實(shí)現(xiàn)Runnable接口的方式編寫(xiě)線程執(zhí)行的代碼,具體實(shí)現(xiàn)代碼如下:
Thread thread=new Thread(new Runnable(){
@Override
public void run(){
System.out.println("Runnable接口方式實(shí)現(xiàn)多線程");
}
});
一個(gè)數(shù)據(jù),如一個(gè)對(duì)象或?qū)ο笾械哪硞€(gè)字段,如果有多個(gè)線程可以同時(shí)訪問(wèn)它,就可能會(huì)出現(xiàn)線程安全問(wèn)題:數(shù)據(jù)錯(cuò)亂、程序出錯(cuò)或其他無(wú)法預(yù)知的問(wèn)題.比如線程1要遍歷一個(gè)list集合,線程2要把這個(gè)list集合清空,如果這兩個(gè)線程同時(shí)執(zhí)行就可能會(huì)出現(xiàn)線程安全問(wèn)題.線程同步控制,即使用某種方式使得一個(gè)線程在操作完某個(gè)數(shù)據(jù)前,別的線程無(wú)法操作這個(gè)數(shù)據(jù),從而避免多個(gè)線程同時(shí)操作一個(gè)數(shù)據(jù),進(jìn)而避免線程安全問(wèn)題.
在Java中每個(gè)對(duì)象都有一把鎖,同一時(shí)刻只能有一個(gè)線程持有這把鎖,線程可以使用synchronized關(guān)鍵字向系統(tǒng)申請(qǐng)某個(gè)對(duì)象的鎖,得到鎖之后,別的線程再申請(qǐng)?jiān)撴i時(shí),就只能等待.持有鎖的線程在這次操作完成后,可以釋放鎖,以便其他線程可以獲得鎖.例如,以synchronized代碼塊實(shí)現(xiàn)同步鎖機(jī)制的主要代碼如下:
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();
}
}
});
對(duì)于稍復(fù)雜的情況,比如多個(gè)線程需要相互合作有規(guī)律的訪問(wèn)共享數(shù)據(jù),就可以使用wait/notify機(jī)制,即等待/通知機(jī)制,也稱(chēng)等待/喚醒機(jī)制.
等待/通知機(jī)制建立在synchronized同步鎖機(jī)制的基礎(chǔ)上,即在同步代碼塊(或同步方法)內(nèi),如果當(dāng)前線程執(zhí)行了lockObject.wait()(lockObject表示提供鎖的對(duì)象),則當(dāng)前線程立即暫停執(zhí)行,并被放入阻塞隊(duì)列,并向系統(tǒng)歸還所持有的鎖,并在lockObject上等待,直到別的線程調(diào)用lockObject.notify().如果有多個(gè)線程在同一個(gè)對(duì)象上等待,notify()方法只會(huì)隨機(jī)通知一個(gè)等待的線程,也可以使用notifyAll()方法通知所有等待的線程.被通知的線程獲得鎖后會(huì)進(jìn)入就緒隊(duì)列.
假設(shè)線程1需要同時(shí)擁有資源A和資源B才能工作,線程2需要同時(shí)擁有資源A和資源B才能工作,在進(jìn)行同步控制時(shí)有可能出現(xiàn)這種情況:線程1擁有資源A,線程2擁有資源B,兩個(gè)線程相互等待對(duì)方先釋放資源,并會(huì)一直這么僵持下去,這種情況稱(chēng)為死鎖.
為了避免死鎖,可以使用信號(hào)量機(jī)制:線程在嘗試申請(qǐng)某個(gè)資源前都要判斷能否一次性就獲得所有需要的資源,如果能,就申請(qǐng),如果不能,則不申請(qǐng),一直等到可以一次性獲得所有資源.
網(wǎng)絡(luò)編程,主要是指基于TCP的網(wǎng)絡(luò)通信編程,在Java中網(wǎng)絡(luò)編程是使用Socket類(lèi)實(shí)現(xiàn),因此也稱(chēng)為socket編程.socket編程模型中有服務(wù)器端和客戶端,服務(wù)器端使用ServerSocket創(chuàng)建,一般有固定的IP地址和端口號(hào),方便向外界提供服務(wù).客戶端可以有多個(gè),并且使用Socket主動(dòng)連接服務(wù)器.連接后,服務(wù)器端也創(chuàng)建一個(gè)Socket對(duì)象表示這次連接.
在Java中實(shí)現(xiàn)socket編程,服務(wù)器端要做的事情主要有:創(chuàng)建服務(wù)器對(duì)象ServerSocket;等待客戶端的連接請(qǐng)求,收到請(qǐng)求后即返回表示這次連接的Socket對(duì)象;開(kāi)啟新的線程專(zhuān)門(mén)處理這個(gè)連接;獲得連接的輸入輸出流,并按照一定的規(guī)則進(jìn)行數(shù)據(jù)交換;關(guān)閉連接(關(guān)閉連接時(shí)會(huì)自動(dòng)關(guān)閉IO流).服務(wù)器端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中實(shí)現(xiàn)socket編程,客戶端要做的事情主要有:創(chuàng)建Socket對(duì)象,即向服務(wù)器申請(qǐng)連接;獲得連接的輸入輸出流,并按照一定的規(guī)則進(jìn)行數(shù)據(jù)交換;最后關(guān)閉連接(關(guān)閉連接時(shí)會(huì)自動(dòng)關(guān)閉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();
}
}
赤峰學(xué)院學(xué)報(bào)·自然科學(xué)版2018年9期