多线程同步的缘由简述

2020-11-12 0 By admin

当多个线程同时对某一个数据区或内存位置进行操作时,如果不施加线程同步的措施,很可能造成数据操作混乱。如同数据库中的脏读、不可重复读和幻读等情况。

一、多线程间的数据竞争

当只有一个线程访问数据时,数据竞争基本不会存在;只有多线程同时访问数据时,才会发生数据竞争。
数据竞争问题是有至少两个同时执行的线程访问同一个内存位置,并且至少有一个线程尝试写入数据而引起的问题。

1.1、同步机制

为了避免数据竞争,通常需要在程序中加入同步机制,以保证数据访问的正确性。

  1. 有些同步机制(如:锁)可以保证数据在某一时间内只有一个线程访问,
  2. 有些同步机制(如:软件事务性内存)可以让数据由多个线程操作,虽然多线程同时访问,但是会保证最早提交的数据有效,其他的数据操作要回滚。

二、临界区

某一段被多个线程共享的数据区域,线程必须对它进行互斥访问;线程中访问共享数据的那段代码被称为临界区(Critical Section)。线程进入临界区需要遵循一定的原则。

  1. 多个线程可以同时请求进入临界区,但同一时刻只允许一个线程进入。
  2. 当临界区被一个线程拥有时,其他线程需要等待,不允许进入该临界区。
  3. 临界区内的操作应该在有限的时间内完成,以便给其他线程运行的机会。
  4. 一个线程执行完临界区后,操作系统随机选取一个线程进入,其他未被选取的线程继续等待。

为了帮助程序员实现临界区,Java 提供了同步机制。当一个线程试图访问临界区时,同步机制会判断当前是否有其他线程正在使用临界区。

三、监视器

在 Java 语言中,监视器(Monitor)具有如下特征:

  1. 一个监视器是只有一个私有属性的类。
  2. 每个监视器类的对象实例都有一个相关联的锁。这个锁将对对象所有的方法加锁。方法调用开始时,自动获取对象的锁;方法执行完成后解锁。
  3. Java 中每个对象都有一个隐式的锁。

四、阻塞和非阻塞

当线程请求某一种资源时,如果线程的请求得不到响应,线程可以采用多种方式来决定接下来要采取的动作。

  1. 线程可以采用一直尝试的方式,在每次请求资源得不到得不到满足的情况下,下一次依然继续请求,直到请求获得满足。采用这种方式的线程处于一种非阻塞状态,如果资源被占用的时间过长,这种方式必然会导致CPU资源的浪费。
  2. 另外一种方式:线程并不是一直等待,而是被阻塞;这样CPU的资源可以让出来执行一些其他的操作。线程被阻塞表示线程可能被CPU挂起,等待某一时间后,再去尝试获取资源。

五、线程安全和线程不安全

在程序设计过程中,一般要先保证程序的正确性,其次才是提高程序的性能。
在传统的串行执行的程序中,程序往往有一个固定的执行次序,对于数据的访问操作也是有顺序的。然而,在多线程程序中,必须保证数据被多个线程操作是安全的。

5.1、一个对象是否是线程安全的?

当多线程同时访问某个类时,不管线程之间如果交替执行,总能够得到正确的执行结果,则称这个类是线程安全的;否则是线程不安全的。

5.2、线程安全的对象要符合什么条件?

要编写线程安全的代码,需要特别注意哪些共享的(Shared)和可变的(Mutable)数据或状态的操作。
共享意味着变量可以被对个线程所访问,可变意味着变量的值在其生命周期内会发生变化。
线程安全的代码需要采用同步机制来控制对于共享的或者可变的变量的访问,特别是多个线程中至少存在一个写操作的情况下。

5.3、补充

Java 工具集合中提供的类有些是线程安全的(如:HashTable),有的则不是(如:HashMap)。一般在线程安全的类中已经封装了必要的同步控制机制,因此不必进一步采取同步控制措施。