郑逸凡
(福州外语外贸学院,福建 福州 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();
}
}