多线程 (四) 同步--非常重要

同步非常非常重要,我们在处理多线程问题时,基本上都要使用Synchronized关键字。

因此必须重点理解。

 

首先,多线程会出现问题的根本原因是数据共享。

Threads communicate primarily by sharing access to fields and the objects reference fields refer to. This form of communication is extremely efficient, but makes two kinds of errors possible:thread interference and memory consistency errors . The tool needed to prevent these errors is synchronization .

 

这篇文章将要谈到的问题是,为什么多线程会有问题,以及怎么解决。

 

Thread interference 线程间干扰

一个简单的例子:一个class有一个成员变量,两个方法,一个加一个减,用成员变量保存运算结果,用get方法提供给外部代码。

Threads communicate primarily by sharing access to fields and the objects reference fields refer to。也就是说,多个线程操作的是同一个对象。

即使是最简单的c++, c--操作,实际上也不是原子操作(Atomic access)。在执行时,实际可能是以下步骤:

1, 取到c的值。

2, 加1。

3, 写回c的值。

而多任务操作系统的特点就是:线程会随时被挂起,cpu将时间slice交给另外一个线程执行。假如上述步骤中应执行3的时候被挂起,另一个线程修改了c的值。之后第一个线程又被唤醒,写回了c值。那样第二个线程的修改就丢失了。

Java的Object基类实现了一个monitor,本质上是一种内置锁,因此所有Object的子类都具有该功能,实例化出来的每一个对象都有这个锁。当一个线程需要操作对象的时候,需要先检查对象的锁是否被占用,如果被占用则需要等待,如果未被占用则取得这个锁。

我们可以看到之后将要谈到的同步synchronized,在本质上就是:在一个线程取得内置锁,执行代码段完毕并释放内置锁之前,该线程的操作绝对不会被干扰(线程一样可能在执行代码段时被挂起,但是由于该线程持有内置锁,因此其他线程绝对无法改变共享对象)。如此就保证了一个线程一定能完整的,不被干扰的执行完一段代码,就形成了类似原子操作。

 

Memory consistency error也会产生问题,但是原因复杂没有详述。

避免这种error的方法就是使用happens-before relationships.  希望一个线程一定在另一个线程前执行。

 

synchronized

解决以上两种问题的方法就是使用synchronized 关键字。

Java有两种synchronization idioms:synchronized method and synchronized statement。

  • 同步方法 synchronized method
public class SynchronizedCounter {

  private int c = 0;

  public synchronized void increment() {
      c++;
      //仅仅需要简单的在方法前添加synchronized关键字
  }

  public synchronized void decrement() {
     c--;
}

  public synchronized int value() {
     return c;
  }

}

//这样在线程调用SynchronizedCounter 类的实例的方法时,只有先获得这个实例的锁,才能运行这几个方法。也就是说,在一个线程执行完同步方法,并释放该实例的内部锁前,其他线程都会被阻塞。

//注意只有多个线程试图操作同一个类的实例对象时,synchronized 才会发挥作用。

//在构造函数上使用synchronized关键字是没有意义的,因为synchronized对于每一个实例才有意义。

//final 成员变量也不需要同步方法. which cannot be modified after the object is constructed。

 

下面是关于使用同步方法要注意的两点:

  • First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.
  • Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.

进一步解释synchronized工作的原理是:

只有当一个线程取得了实例对象的锁(intrinsic lock)后,这个线程才能执行类中的synchronized方法

当一个线程占有一个对象的锁时,其它线程无法取得该锁,直到第一个线程释放锁。

一个线程企图调用一个synchronized方法时,它会自动请求对象的锁,同时在使用完毕后释放。

不管该线程执行synchronized方法是否正常结束(例如遇到了异常),仍然会释放锁。

静态同步方法让人有些奇怪,因为静态方法是属于类,而不是实例的。使用静态同步方法会控制线程对该类的静态成员变量的访问。

 

  • 同步语句 synchronized statement

上面取得对象锁的同步方案隐含了一个问题:一个对象上的所有同步方法都只能串行执行,即使其中一些同步方法并不是互斥的即使同时运行也没问题。另外如果一个同步方法操作了多个对象,第一个对象锁被占用会导致整个线程被挂起,block了对其他对象的操作。因此使用同步方法的本质是:方法中所有操作成为了一个原子操作,成为了一个整体,是以牺牲性能为代价的。

另外一种实现同步的方法就是同步语句。同步语句必须指明提供内部锁的对象:

public void addName(String name) {
    synchronized(this) {  //即就是当前实例
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

在这个例子中,实现的是同步对当前实例的lastName 和nameCount的修改,但是没有限制对另外一个对象nameList.add调用。实现了更细粒度的控制。而如果是synchronized method,则无法实现,必须将原方法劈成两个。

所以同步语句很实用,因为一个方法操作的可能是多个对象实例。

使用同步语句的另一个好处就是可以提高并发度。比如下面的例子:c1和c2之间没有任何关系,我们需要的是不同线程不能同时修改c1或c2,但是如果使用同步方法就会导致一个线程在修改c1时,其他线程也不能修改c2了。因为同步方法获得的是整个对象实例的锁。

public class MsLunch {

    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
      synchronized(lock1) {
      c1++;
  }
}
  public void inc2() {

    synchronized(lock2) {
    c2++;
   //通过提供两个不用的对象lock1和lock2,线程修改两个变量就可以互相不影响了。
   //这种解决办法的本质是:在同步方法中只能使用object的内置锁,因此无法实现对不同成员变量的分别控制
   //而同步代码块则是自己指定要使用的锁,并实现控制分离。
  }
  }
}

 

  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值