进程与线程及其通信方式

进程和线程的区别

  • 调度:线程作为调度和分配的基本单位,进程作为资源分配的基本单位。
  • 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。
  • 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
  • 系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

进程间的通信方式

  1. 管道(pipe)及有名管道(named pipe)
    传递数据是单向性的,管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

  2. 信号(signal)
    信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源。信号分为可靠信号和不可靠信号,实时信号和非实时信号。进程有三种方式响应信号:1.忽略信号2.捕捉信号3.执行缺省操作。

  3. 消息队列(message queue)
    消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新信息;对消息队列有读权限的进程则可以从消息队列中读取信息。

  4. 共享内存(shared memory)
    可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。如两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。

  5. 信号量(semaphore)
    也可以说是一个计数器,常用来处理进程或线程同步的问题,特别是对临界资源的访问同步问题。临界资源:为某一时刻只能由一个进程或线程操作的资源,当信号量的值大于或等于0时,表示可以供并发进程访问的临界资源数,当小于0时,表示正在等待使用临界资源的进程数。信号量主要作为进程之间及同一种进程的不同线程之间的同步和互斥手段。

  6. 套接字(socket)
    这是一种更为一般的进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

线程之间的同步通信

  1. 同步
    多个线程通过synchronized关键字这种方式来实现线程间的通信。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class MyObject {

synchronized public void methodA() {
//do something....
}

synchronized public void methodB() {
//do some other thing
}
}

public class ThreadA extends Thread {

private MyObject object;
//省略构造方法
@Override
public void run() {
super.run();
object.methodA();
}
}

public class ThreadB extends Thread {

private MyObject object;
//省略构造方法
@Override
public void run() {
super.run();
object.methodB();
}
}

public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();

//线程A与线程B 持有的是同一个对象:object
ThreadA a = new ThreadA(object);
ThreadB b = new ThreadB(object);
a.start();
b.start();
}
}

由于线程A和线程B持有同一个MyObject类的对象object,尽管这两个线程需要调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了通信。

  1. while轮询的方式
    在这种方式下,如线程A不断地改变条件,线程B不停地通过while语句检测这个条件(条件判断)是否成立,从而实现了线程间的通信。但是这种方式会浪费CPU资源。之所以说它浪费资源,是因为JVM调度器将CPU交给线程B执行时,它没做啥”有用”的工作,只是在不断地测试某个条件是否成立。就类似于现实生活中,某个人一直看着手机屏幕是否有电话来了,而不是:在干别的事情,当有电话来时,响铃通知TA电话来了。

  2. wait/notify/notifyAll机制
    这种方式,本质上就是”共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* wait / notify 机制实现通信
*/
class MyList {

private static List<String> list = new ArrayList<String>();

public static void add() {
list.add("anyString");
}

public static int size() {
return list.size();
}
}
class ThreadA extends Thread {

private Object lock;

public ThreadA(Object lock) {
super();
this.lock = lock;
}

@Override
public void run() {
try {
synchronized (lock) {
if (MyList.size() != 5) {
System.out.println("wait begin "
+ System.currentTimeMillis());
lock.wait();
System.out.println("wait end "
+ System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

class ThreadB extends Thread {
private Object lock;

public ThreadB(Object lock) {
super();
this.lock = lock;
}

@Override
public void run() {
try {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
MyList.add();
if (MyList.size() == 5) {
lock.notify(); // A线程已被唤醒,但是需要当前线程执行完之后,它再执行
System.out.println("已经发出了通知");
}
System.out.println("添加了" + (i + 1) + "个元素!");
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {

try {
Object lock = new Object();

ThreadA a = new ThreadA(lock);
a.start();
Thread.sleep(50);
ThreadB b = new ThreadB(lock);
b.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

互斥

从对资源的访问来看的话,互斥意味着某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步

从对资源的访问来看的话,同步意味着是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

知识点回顾

  • 一个程序至少有一个进程,一个进程至少有一个线程。
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存
  • 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
  • 进程是表示资源分配的基本单位,线程是进程中执行运算(调度运行)的基本单位。线程是指进程内的一个执行单元,也是进程内的可调度实体。
  • 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
  • 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。因此,实现并发功能的单位是线程。

参考文章:

进程与线程的区别 进程的通信方式 线程的通信方式
JAVA多线程之线程间的通信方式