Practical Java(重点版)之多线程

 

1. 面对instance 函数,synchronized 锁定的是对象(objects)而非函数(methods)或代码(code)。

   Synchronized既可以作用于方法修饰,也可以用于方法内的修饰。对于instance 函数,关键词synchronized 其实并不锁定方法或代码,它锁定的是对象(至于synchronized 对statics 的影响,请见下面)。记住,每个对象只有一个lock(机锁)与之相关联。当synchronized 被当作函数修饰符的时候,它所取得的lock 将被交给函数调用者(某对象)。如果synchronized 用于object reference,则取得的lock 将被交给该reference所指对象。分析如下:

public class Test {

   public synchronized void method1() {//修饰方法

 

   }

 

   public void method2() {//修饰object reference

      synchronized (this) {

      }

   }

  

   public void method3(Object object) {//修饰object reference

      synchronized (object) {

      }

   }

}

前两个函数method1()和method2()在[对象锁定]方面功能一致。 二者都对this进行同步控制。换句话说,获得的lock 将给予调用此函数的对象(也就是this)。由于这两个函数都隶属class Test,所以lock 由Test 的某个对象获得。method3()则同步控制object所指的那个对象。

对一个对象进行同步控制到底意味什么呢?它意味[调用该函数]之线程将会取得对象的lock。持有[对象A 之lock],意味另一个通过synchronized 函数或synchronized语句来申请[对象A 之lock]的线程,在该lock 被释放之前将无法获得满足。然而如果另一个线程对对象A 所属类之另一对象B 调用相同的synchronized 函数或synchronized 区块,可以获得[对象B 之lock]。因此,synchronized 函数或synchronized 区段内的代码在同一时刻下可由多个线程执行,只要是对不同的对象调用该函数。

记住,同步机制(synchronization)锁定的是对象,而不是函数或代码。函数或代码区段被声明为synchronization 并非意味它在同一时刻只能由一个线程执行。

最后一点说明是:Java 语言不允许你将构造函数声明为,synchronized(那么做会发生编译错误)。原因是当两个线程并发调用同一个构造函数时,它们各自操控的是同一个class 的两个不同实体(对象)的内存(也就是说synchronized 是画蛇添足之举)。然而如果在这些构造函数内包含了彼此竞争共享资源的代码(比如说静态变量),则必须同步控制那些资源以回避冲突。

2. 弄滇楚synchronized statics函数(同步静态函数)与synchronized instance函数(同步实例函数)之间的差异。

当调用synchronized statics方法时,获取到的lock是与定义该方法的Class对象相关,而不是与调用该方法的对象有关。当你对一个class literal(类名称字面常量)调用其synchronized 区段时,获得的也是同样那个lock,也就是[与特定Class 对象相关联]的lock。

如下:

public class Thread01 {

   public static synchronized void method() {

 

   }

 

   public void method1() {

      synchronized (Thread01.class) {

 

      }

   }

}

method()和method1()都是争取的同一个lock,也就是Thread01 Class object lock。method()通过synchronized的修饰符来获取lock,而method2()是通过class literal Thread01.class来获取lock的。

如果synchronized 施行于instance 函数和object references,得到的lock 就与前面的不一样了。对于instance 函数,取得的lock 隶属于其调用者(某个对象),至于同步控制一个(指名)对象,取得的当然是该对象的lock。

由于同步控制(1)instance 函数(2)static 函数(3)对象(object) (4)class literals 时得到的locks 不同,因此在决定互(mutual exclusion)行为时一定要小心谨慎。记住,同步控制[通过instance 函数或object reference 所取得的lock]。完全不同于同步控制[通过static 函数或class literal 所取得的lock]。两个函数被声明为synchronized 并不就意味它们具备多线程安全性。你必须小心识别和区分通过同步控制所取得的locks 之间的微妙差异。

如下:

class Thread02 implements Runnable {

 

   public synchronized void printlnM1() {

      while (true) {

        System.out.println("printlnM1");

      }

   }

 

   public static synchronized void printlnM2() {

      while (true) {

        System.out.println("printlnM2");

      }

   }

 

   @Override

   public void run() {

      printlnM1();

   }

}

 

class TestThread {

   public static void main(String[] args) {

      Thread02 t = new Thread02();

      Thread f = new Thread(t);

      f.start();

      t.printlnM2();

   }

}

尽管上述两个函数都声明为synchronized,它们并非[多线程安全](thread safe)。

其原因在于一个是synchronized static 函数,另一个是synchronized instance函数。因此它们争取的是不同的locks。instance 函数printlnM1()取得的是Thread02 object lock,static 函数printlnM2()取得的是Thread02 的Class object lock。这是不同的两个locks,彼此互不影响。当上述代码执行起来,两个字符串都打印在屏幕上。换句话讲,两个函数的执行交互穿插。如果需要同步控制这段代码,可以共享同一个资源。为了保护这笔资源,代码必须有正确的同步控制,以避免冲突。两种选择可以解决这个问题:

1.同步控制(synchronize)公用资源。

2. 同步控制(synchronize)—个特殊的instance 变量。

方案一实现:前提假设两个函数要更新同一个对象,它们就对其进行同步控制。

  class Thread02 implements Runnable {

   private Object o;

 

   public synchronized void printlnM1() {

      synchronized (o) {

        while (true) {

           System.out.println("printlnM1");

        }

      }

   }

 

   public static synchronized void printlnM2(Thread02 o) {

      synchronized (o.o) {

        while (true) {

           System.out.println("printlnM2");

        }

      }

   }

 

   @Override

   public void run() {

      printlnM1();

   }

}

方案二实现:声明一个local instance 变量,惟一的目的就是对它进行同步控制。

class Thread02 implements Runnable {

   private byte[] lock = new byte[0];

 

   public synchronized void printlnM1() {

      synchronized (lock) {

        while (true) {

           System.out.println("printlnM1");

        }

      }

   }

 

   public static synchronized void printlnM2(Thread02 o) {

      synchronized (o.lock) {

        while (true) {

           System.out.println("printlnM2");

        }

      }

   }

 

   @Override

   public void run() {

      printlnM1();

   }

}由于只能锁定对象,你使用的local instance 变量必须是个对象。

3. 以[private数据+相应的访问函数(accessor)]替换[public/protected数据。这样做是为了封装。记住,对于[在synchronized 函数中可被修改的数据],应使之成为private,并根据需要提供访问函数(accessor)。如果访问函数返回是可变对象(mutable object),那么应该先cloned(克隆)该对象。

4. 要避免无所谓的同步控制。过度的同步控制估计会的导致死锁(deadlocks)或者并发(concurrency)度降低。同步机制对每个对象只提供一个lock。当一个函数声明为synchronized,所获得的lock 乃是隶属于调用此函数的那个对象。如果该对象要访问其他方法的话,就必须释放lock,这样的话性能就降低了,这时可以再添加一个变量来产生不同的lock。如下:

class Thread03{

      private int[] a1;

  private int[] a2;

  private double[] d1;

      private double[] d2;

 

  private byte[] a = new byte[0];

  private byte[] d = new byte[0];

 

  public void ma1(){

      synchronized (a) {

       

     }

  }

  

      public void ma2(){

      synchronized (a) {

       

     }

  }

 

      public void md1(){

     synchronized (d) {

       

     }

  }

  public void md2(){

     synchronized (d) {

      

      }

}

}

 

 

5. 访问共享变量时请使用synchronized或volatile。可以确保变量与主内存完全保持一致,从而在任何时候得到正确数值。记住,一旦变量被声明为volatile,在每次访问它们时,它们就与主内存进行一致化。但如果使用synchronized,只有在取得lock 和释放lock 的时候,才会对变量和主内存进行一致化。

 

优点

缺点

synchronized

取得和释放lock 时,进行私

有专用副本

与主内存正本的一致化

消除了并发性的可能

volatile

允许并发

每次访问变量,就进行稀有专用内存与对

应之主内存的一致化

 

6. 在单一操作中锁定所有用到的对象。

7. 以固定而全局性的顺序取得多个locks(机锁)以避免死锁。

死锁:当两个或者多个线程因为互相等待而阻塞。

8. 优先使用notifyAll()而非notify()。

9. 针对wait()和notifyAll()使用旋锁(spin locks)。只在代码等待着某个特定条件,它就应当在一个循环内(或谓旋锁,spin lock)做那件事(那件事指的是[等待着某个特定条件])。尤其是在判断null之类的时候,因为不能够确保多个线程被唤醒的时候,其条件是否为空。

10.  使用wait()和notifyAll()替换轮询循环(polling loops)。

11. 不要对locked object(上锁对象)之object reference重新赋值。

12. 不要调用stop()或suspend()。

Stop()的本意是用来中止一个线程。中止线程的问题根源不在object locks,而在object 的状态。当stop()中止一个线程时,会释放线程持有的所有locks。但是你并不知道当时代码正在做什么。

Suspend()的本意时用来[暂时悬挂起一个线程](它由一个对应的resume()函数,用来恢复先前被悬挂起来的线程。Resume()也不再获得Java 2 SDK 支持)。Suspend()同样时不安全的,但其原因和stop()的故事不同。和stop()不同,suspend()并不释放[即将被悬挂支线程说持有的locks]。这些locks 在线程恢复执行前永远不会释放。

stop()带来[搅乱内部数据]的风险,suspend()带来死锁的风险。

13. 通过线程(threads)之间的协作来中止线程。

如何中止线程呢?在class 内提供一个变量,以及一个用来设置此变量值的函数。该变量用来表示线程何时应该被中止。

class Thread04 extends Thread {

   private volatile boolean stop;

 

   public void stopFlag() {

     stop = true;

   }

 

   @Override

   public void run() {

     while (stop) {

        super.run();

     }

   }

}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值