软件开发-理论性知识(四)

本文详细探讨了进程和线程的概念及其区别,包括地址空间、资源拥有等方面,并介绍了进程间和线程间的通信机制。此外,还具体分析了Java和Android平台下的线程实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.进程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

2.线程

线程是指进程内的一个执行单元,也是进程内的可调度实体.

3.进程、线程区别

(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
(2)资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是.
(4)二者均可并发执行.

(5)一个程序至少有一个进程,一个进程至少有一个线程.
(6) 线程的划分尺度小于进程,使得多线程程序的并发性高。
(7)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
(8)线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
(9) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

(10)进程线程占用资源如表:

进程占有的资源 线程占有的资源
地址空间 
全局变量 
打开的文件 
子进程 
信号量 
账户信息
栈 
寄存器 
状态 
程序计数器

4.并行与并发的区别

(1)并行是指在同一时刻,有多条指令在多个处理器上同时执行。
(2)并发是指在同一时刻,只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。 

5.进程间通信

进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。系统空间却是“公共场所”,所以内核显然可以提供这样的条件。

linux下的进程间通信方式:

(1)管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
(2)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
(3)报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(4)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
(5)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
(6)套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

window下的进程间通信方式:

(1) 文件映射
文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待。因此,进程不必使用文件I/O操作,只需简单的指针操作就可读取和修改文件的内容。
Win32 API允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。
应用程序有三种方法来使多个进程共享一个文件映射对象。
(a)继承:第一个进程建立文件映射对象,它的子进程继承该对象的句柄。
(b)命名文件映射:第一个进程在建立文件映射对象时可以给该对象指定一个名字(可与文件名不同)。第二个进程可通过这个名字打开此文件映射对象。另外,第一个进程也可以通过一些其它IPC机制(有名管道、邮件槽等)把名字传给第二个进程。
(c)句柄复制:第一个进程建立文件映射对象,然后通过其它IPC机制(有名管道、邮件槽等)把对象句柄传递给第二个进程。第二个进程复制该句柄就取得对该文件映射对象的访问权限。
文件映射是在多个进程间共享数据的非常有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步。
(2) 共享内存
Win32 API中共享内存(Shared Memory)实际就是文件映射的一种特殊情况。进程在创建文件映射对象时用0xFFFFFFFF来代替文件句柄(HANDLE),就表示了对应的文件映射对象是从操作系统页面文件访问内存,其它进程打开该文件映射对象就可以访问该内存块。由于共享内存是用文件映射实现的,所以它也有较好的安全性,也只能运行于同一计算机上的进程之间。
(3)匿名管道
管道(Pipe)是一种具有两个端点的通信通道:有一端句柄的进程可以和有另一端句柄的进程通信。管道可以是单向-一端是只读的,另一端点是只写的;也可以是双向的一管道的两端点既可读也可写。
匿名管道(Anonymous Pipe)是 在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写 端点句柄,然后实现通信。父进程还可以建立两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可以使用管道直接通信,不需要通过父进程。
匿名管道是单机上实现子进程标准I/O重定向的有效方法,它不能在网上使用,也不能用于两个不相关的进程之间。
(4)命名管道
命名管道(Named Pipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。不同于匿名管道的是命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。
命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。
(5) 邮件槽
邮件槽(Mailslots)提 供进程间单向通信能力,任何进程都能建立邮件槽成为邮件槽服务器。其它进程,称为邮件槽客户,可以通过邮件槽的名字给邮件槽服务器进程发送消息。进来的消 息一直放在邮件槽中,直到服务器进程读取它为止。一个进程既可以是邮件槽服务器也可以是邮件槽客户,因此可建立多个邮件槽实现进程间的双向通信。
通过邮件槽可以给本地计算机上的邮件槽、其它计算机上的邮件槽或指定网络区域中所有计算机上有同样名字的邮件槽发送消息。广播通信的消息长度不能超过400字节,非广播消息的长度则受邮件槽服务器指定的最大消息长度的限制。
邮件槽与命名管道相似,不过它传输数据是通过不可靠的数据报(如TCP/IP协议中的UDP包)完成的,一旦网络发生错误则无法保证消息正确地接收,而命名管道传输数据则是建立在可靠连接基础上的。不过邮件槽有简化的编程接口和给指定网络区域内的所有计算机广播消息的能力,所以邮件槽不失为应用程序发送和接收消息的另一种选择。
(6)剪贴板
剪贴板(Clipped Board)实质是Win32 API中一组用来传输数据的函数和消息,为Windows应用程序之间进行数据共享提供了一个中介,Windows已建立的剪切(复制)-粘贴的机制为不同应用程序之间共享不同格式数据提供了一条捷径。当用户在应用程序中执行剪切或复制操作时,应用程序把选取的数据用一种或多种格式放在剪贴板上。然后任何其它应用程序都可以从剪贴板上拾取数据,从给定格式中选择适合自己的格式。
剪贴板是一个非常松散的交换媒介,可以支持任何数据格式,每一格式由一无符号整数标识,对标准(预定义)剪贴板格式,该值是Win32 API定义的常量;对非标准格式可以使用Register Clipboard Format函数注册为新的剪贴板格式。利用剪贴板进行交换的数据只需在数据格式上一致或都可以转化为某种格式就行。但剪贴板只能在基于Windows的程序中使用,不能在网络上使用。

(7)Sockets、WM_COPYDATA消息、动态链接库等

6.线程间通信

 线程间无需特别的手段进行通信,因为线程间可以共享数据结构,也就是一个全局变量可以被两个线程同时使用,不过要注意的是线程间需要做好同步。

7.JAVA中的线程实现方式

JAVA线程实现方式:1)、继承Thread类,重写run方法 2)、实现Runnable接口3)、使用ExecutorService、Callable、Future实现有返回结果的多线程

1)继承Thread类

public MyThread extends Thread{

public void run(){

}

}

MyThread thr = new MyThread();

thr.start();

2)实现Runnable接口

public MyThread implements Runnable{

public void run(){

}

}

MyThread mythr = new MyThread();

Thread thr = new Thread(mythr);

thr.start();

3)使用ExecutorService、Callable、Future实现有返回结果的多线程

import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;

/**
* Java线程:有返回值的线程
* 
* @author wb_qiuquan.ying
*/
@SuppressWarnings("unchecked")
public class Test {
public static void main(String[] args) throws ExecutionException,
    InterruptedException {
   System.out.println("----程序开始运行----");
   Date date1 = new Date();

   int taskSize = 5;
   // 创建一个线程池
   ExecutorService pool = Executors.newFixedThreadPool(taskSize);
   // 创建多个有返回值的任务
   List<Future> list = new ArrayList<Future>();
   for (int i = 0; i < taskSize; i++) {
    Callable c = new MyCallable(i + " ");
    // 执行任务并获取Future对象
    Future f = pool.submit(c);
    // System.out.println(">>>" + f.get().toString());
    list.add(f);
   }
   // 关闭线程池
   pool.shutdown();

   // 获取所有并发任务的运行结果
   for (Future f : list) {
    // 从Future对象上获取任务的返回值,并输出到控制台
    System.out.println(">>>" + f.get().toString());
   }

   Date date2 = new Date();
   System.out.println("----程序结束运行----,程序运行时间【"
     + (date2.getTime() - date1.getTime()) + "毫秒】");
}
}

class MyCallable implements Callable<Object> {
private String taskNum;

MyCallable(String taskNum) {
   this.taskNum = taskNum;
}

public Object call() throws Exception {
   System.out.println(">>>" + taskNum + "任务启动");
   Date dateTmp1 = new Date();
   Thread.sleep(1000);
   Date dateTmp2 = new Date();
   long time = dateTmp2.getTime() - dateTmp1.getTime();
   System.out.println(">>>" + taskNum + "任务终止");
   return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}

4)Timer和TimerTask可以视为实现线程的第四种方式

 Timer是一种线程设施,用于安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行,可以看成一个定时器,可以调度TimerTask。TimerTask是一个抽象类,实现了Runnable接口,所以具备了多线程的能力。
 一个Timer可以调度任意多个TimerTask,它会将TimerTask存储在一个队列中,顺序调度,如果想两个TimerTask并发执行,则需要创建两个Timer

import java.util.Timer;  
import java.util.TimerTask;  
public class TimerTest {  
    static class MyTimerTask1 extends TimerTask {  
        public void run() {  
            System.out.println("Timer run");  
        }  
    }     
    public static void main(String[] args) {  
        Timer timer = new Timer();  
        timer.schedule(new MyTimerTask1(), 2000);// 两秒后启动任务  
    }  
} 

8.JAVA中线程的start和Run的区别

start()方法为开启了一个新的线程,实现了多线程,start方法只能调用一次;run方法只是普通类方法的调用,即run方法可以被调用多次,未实现多线程

public class Test {
    public static void main(String[] args) {
        Runner runner = new Runner();
        Thread t = new Thread(runner);
        t.start(); //调用start()方法来启动线程s

        runner.run(); //直接通过类调用类中的方法来执行线程的代码
    }
}


class Runner implements Runnable {
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("i = " + i);
        }
    }
}

9.JAVA中的线程同步

1)synchronize关键字只能用于非抽象的方法,不能用于成员变量

2)volatile用于成员变量

被上述两个关键字修饰的方法和成员变量只能在同一时刻被同一个线程访问。

程序示例:

/** 
* Java线程:线程的同步 
* 
* @author leizhimin 2009-11-4 11:23:32 
*/ 
public class Test { 
        public static void main(String[] args) { 
                User u = new User("JAVA线程", 100); 
                MyThread t1 = new MyThread("线程A", u, 20); 
                MyThread t2 = new MyThread("线程B", u, -60); 
                MyThread t3 = new MyThread("线程C", u, -80); 
                MyThread t4 = new MyThread("线程D", u, -30); 
                MyThread t5 = new MyThread("线程E", u, 32); 
                MyThread t6 = new MyThread("线程F", u, 21); 

                t1.start(); 
                t2.start(); 
                t3.start(); 
                t4.start(); 
                t5.start(); 
                t6.start(); 
        } 
} 

class MyThread extends Thread { 
        private User u; 
        private int y = 0; 

        MyThread(String name, User u, int y) { 
                super(name); 
                this.u = u; 
                this.y = y; 
        } 

        public void run() { 
                u.oper(y); 
        } 
} 

class User { 
        private String code; 
        private int cash; 

        User(String code, int cash) { 
                this.code = code; 
                this.cash = cash; 
        } 

        public String getCode() { 
                return code; 
        } 

        public void setCode(String code) { 
                this.code = code; 
        } 

        /** 
         * 业务方法 
         * @param x 添加x万元 
         */ 
        public synchronized void oper(int x) { 
                try { 
                        Thread.sleep(10L); 
                        this.cash += x; 
                        System.out.println(Thread.currentThread().getName() + "运行结束,增加“" + x + "”,当前用户账户余额为:" + cash); 
                        Thread.sleep(10L); 
                } catch (InterruptedException e) { 
                        e.printStackTrace(); 
                } 
        } 

        @Override 
        public String toString() { 
                return "User{" + 
                                "code='" + code + '\'' + 
                                ", cash=" + cash + 
                                '}'; 
        } 
}

10.Android中线程实现方式

1)Activity.runOnUiThread(Runnable)  

activity.runOnUiThread(new Runnable() {               
            @Override 
            public void run() {  
                // TODO Auto-generated method stub  
            	int i = 30;
            	textView.setText(""+i+" s");
            	}
            }  
        });  
2)View.post(Runnable) ;View.postDelay(Runnable , long)  

View.post(Runnable)方法。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。

3)Handler  

Handler在android里负责发送和处理消息。它的主要用途有:
a)按计划发送消息或执行某个Runnanble(使用POST方法);
b)从其他线程中发送来的消息放入消息队列中,避免线程冲突(常见于更新UI线程)

默认情况下,Handler接受的是当前线程下的消息循环实例(使用Handler(Looper looper)、Handler(Looper looper, Handler.Callback callback)可以指定线程),同时一个消息队列可以被当前线程中的多个对象进行分发、处理(在UI线程中,系统已经有一个Activity来处理了,你可以再起若干个Handler来处理)。在实例化Handler的时候,Looper可以是任意线程的,只要有Handler的指针,任何线程也都可以sendMessage。Handler对于Message的处理不是并发的。一个Looper 只有处理完一条Message才会读取下一条,所以消息的处理是阻塞形式的(handleMessage()方法里不应该有耗时操作,可以将耗时操作放在其他线程执行,操作完后发送Message(通过sendMessges方法),然后由handleMessage()更新UI)。

public class MainHandler extends Activity {
  //声明变量
  private Button b1;
  private ProgressDialog pd;
  //定义Handler对象
  private Handler handler =new Handler(){
  @Override
  //当有消息发送出来的时候就执行Handler的这个方法
		public void handleMessage(Message msg){
		  super.handleMessage(msg);
		  //只要执行到这里就关闭对话框
		  pd.dismiss();
  	}
  };
  @Override
  public void onCreate(Bundle savedInstanceState) {
	  super.onCreate(savedInstanceState);
	  setContentView(R.layout.main);
	  Resources res= this.getResources();
	  //查看UI组件所在的线程名
	  Log.i("tag", "onCreate()-->"+Thread.currentThread().getName());
	  //定义UI组件
	  b1= (Button)findViewById(R.id.Button01);
	  //给按钮绑定单击事件监听器
	  b1.setOnClickListener(new View.OnClickListener() {
		  @Override
		  public void onClick(View v) {
			  //点击按钮后去处理长耗时操作
			  processThread();
	  	}
  	});
  }

  private void processThread(){
	  //构建一个下载进度条
	  pd= ProgressDialog.show(MainHandler.this, "下载文件", "正在下载……");
	  Log.i("tag", "processThread()-->"+Thread.currentThread().getName());
	  new Thread(){
		  @Override
		  public void run(){
		  Log.i("tag", "run()-->"+Thread.currentThread().getName());
		  //在新线程里执行长耗时方法
		  longTimeMethod();
		  //执行完毕后给handler发送一个空消息
		  handler.sendEmptyMessage(0);
		  }	
	  }.start();

  }

  //模拟下载文件的长耗时方法
  private void longTimeMethod(){
	  try {
	  Log.i("tag", "longTimeMethod-->"+Thread.currentThread().getName());
	  Thread.sleep(10000);
	  } catch (InterruptedException e) {
	  e.printStackTrace();
  	}
  }
 }

4)AsyncTask  

AsyncTask定义了三种泛型类型 Params,Progress和Result。

  • Params 启动任务执行的输入参数,比如HTTP请求的URL。
  • Progress 后台任务执行的百分比。
  • Result 后台执行任务最终返回的结果,比如String。

AsyncTask 的一个异步加载数据最少要重写以下这两个方法:

  • doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
  • onPostExecute(Result)  相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回

有必要的话还得重写以下这三个方法,但不是必须的:

  • onProgressUpdate(Progress…)   可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。
  • onPreExecute()        这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
  • onCancelled()             用户调用取消时,要做的操作

使用AsyncTask类,以下是几条必须遵守的准则:

  • Task的实例必须在UI thread中创建;
  • execute方法必须在UI thread中调用;
  • 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;
  • 该task只能被执行一次,否则多次调用时将会出现异常;

package vic.wong.main;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends Activity {
	private Button button;
	private ProgressBar progressBar;
	private TextView textView;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        button = (Button)findViewById(R.id.button03);
        progressBar = (ProgressBar)findViewById(R.id.progressBar02);
        textView = (TextView)findViewById(R.id.textView01);
        
        button.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				ProgressBarAsyncTask asyncTask = new ProgressBarAsyncTask(textView, progressBar);
				asyncTask.execute(1000);
			}
		});
    }
}

package vic.wong.main;


//模拟网络环境
public class NetOperator {
	
	public void operator(){
		try {
			//休眠1秒
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

package vic.wong.main;
import android.os.AsyncTask;
import android.widget.ProgressBar;
import android.widget.TextView;

/**
 * 生成该类的对象,并调用execute方法之后
 * 首先执行的是onProExecute方法
 * 其次执行doInBackgroup方法
 *
 */
public class ProgressBarAsyncTask extends AsyncTask<Integer, Integer, String> {

	private TextView textView;
	private ProgressBar progressBar;
	
	
	public ProgressBarAsyncTask(TextView textView, ProgressBar progressBar) {
		super();
		this.textView = textView;
		this.progressBar = progressBar;
	}


	/**
	 * 这里的Integer参数对应AsyncTask中的第一个参数 
	 * 这里的String返回值对应AsyncTask的第三个参数
	 * 该方法并不运行在UI线程当中,主要用于异步操作,所有在该方法中不能对UI当中的空间进行设置和修改
	 * 但是可以调用publishProgress方法触发onProgressUpdate对UI进行操作
	 */
	@Override
	protected String doInBackground(Integer... params) {
		NetOperator netOperator = new NetOperator();
		int i = 0;
		for (i = 10; i <= 100; i+=10) {
			netOperator.operator();
			publishProgress(i);
		}
		return i + params[0].intValue() + "";
	}


	/**
	 * 这里的String参数对应AsyncTask中的第三个参数(也就是接收doInBackground的返回值)
	 * 在doInBackground方法执行结束之后在运行,并且运行在UI线程当中 可以对UI空间进行设置
	 */
	@Override
	protected void onPostExecute(String result) {
		textView.setText("异步操作执行结束" + result);
	}


	//该方法运行在UI线程当中,并且运行在UI线程当中 可以对UI空间进行设置
	@Override
	protected void onPreExecute() {
		textView.setText("开始执行异步线程");
	}


	/**
	 * 这里的Intege参数对应AsyncTask中的第二个参数
	 * 在doInBackground方法当中,,每次调用publishProgress方法都会触发onProgressUpdate执行
	 * onProgressUpdate是在UI线程中执行,所有可以对UI空间进行操作
	 */
	@Override
	protected void onProgressUpdate(Integer... values) {
		int vlaue = values[0];
		progressBar.setProgress(vlaue);
	}

	
	
	

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风水月

从心底相信自己是成功的第一步

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值