【转】Android - 线程同步

什么是线程同步?

当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。

实现同步机制有两个方法:
1、同步代码块:
synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据。

 

 

通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征:
1,该类的对象可以被多个线程安全的访问。
2,每个线程调用该对象的任意方法之后,都将得到正确的结果。
3,每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。
注:synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等

 

※不要对线程安全类的所有方法都进行同步,只对那些会改变共享资源方法的进行同步。

线程通讯:

当 使用synchronized 来修饰某个共享资源时(分同步代码块和同步方法两种情况),当某个线程获得共享资源的锁后就可以执行相应的代码段,直到该线程运行完该代码段后才释放对该 共享资源的锁,让其他线程有机会执行对该共享资源的修改。当某个线程占有某个共享资源的锁时,如果另外一个线程也想获得这把锁运行就需要使用wait() 和notify()/notifyAll()方法来进行线程通讯了。
Java.lang.object 里的三个方法wait() notify()  notifyAll()


wait()


wait(mills)

notify()
唤醒在同步监视器上等待的单个线程,如果所有线程都在同步监视器上等待,则会选择唤醒其中一个线程,选择是任意性的,只有当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。

notifyAll()

 

 

==================================================================================================

 

 

原子操作:根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括longdouble, 因为JVM看到的基本存储单位是32位,而long double都要用64位来表示。所以无法在一个时钟周期内完成。 

自增操作(++)不是原子操作,因为它涉及到一次读和一次写。 

原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。 

同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。 

同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。 

当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。 
1、 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。 
2、 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。 (一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁 如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中) 


线程同步的特征: 
1、 如 果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象 的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制) 
2、 每个对象都有唯一的同步锁 
3、 在静态方法前面可以使用synchronized修饰符。 
4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。 
5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。 

释放对象的锁: 
1、 执行完同步代码块就会释放对象的锁 
2、 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放 
3、 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。 

死锁:

线程1独占(锁定)资源A,等待获得资源B后,才能继续执行
同时
线程2独占(锁定)资源B,等待获得资源A后,才能继续执行
这样就会发生死锁,程序无法正常执行

 

如何避免死锁 
一个通用的经验法则是:当几个线程都要访问共享资源AB时,保证每个线程都按照同样的顺序去访问他们。

 

 

==================================================================================================


 

注意:

1、线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。

2、只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。
3、只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。
4、多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。


同步锁:

    我们可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。

同步锁不是加在共享资源上,而是加在访问共享资源的代码段上。

访问同一份共享资源的不同代码段,应该加上同一个同步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。
这就是说,同步锁本身也一定是多个线程之间的共享对象。

1 public static final Object lock1 = new Object();
2 
3 … f1() {
4 
5 synchronized(lock1){ // lock1 是公用同步锁
6   // 代码段 A
7 // 访问共享资源 resource1
8 // 需要同步
9 }

 

你不一定要把同步锁声明为static或者public,但是你一定要保证相关的同步代码之间,一定要使用同一个同步锁。
任 何一个Object Reference都可以作为同步锁。我们可以把Object Reference理解为对象在内存分配系统中的内存地址。因此,要保证同步代码段之间使用的是同一个同步锁,我们就要保证这些同步代码段的 synchronized关键字使用的是同一个Object Reference,同一个内存地址。这也是为什么我在前面的代码中声明lock1的时候,使用了final关键字,这就是为了保证lock1的 Object Reference在整个系统运行过程中都保持不变。

竞争同步锁失败的线程进入的是该同步锁的就绪(Ready)队列,而不是后面要讲述的待召队列(Waiting Queue,也可以翻译为等待队列)。就绪队列里面的线程总是时刻准备着竞争同步锁,时刻准备着运行。而待召队列里面的线程则只能一直等待,直到等到某个 信号的通知之后,才能够转移到就绪队列中,准备运行。

同步粒度
在Java语言里面,我们可以直接把synchronized关键字直接加在函数的定义上。
比如。

 1synchronized … f1() {
 2   // f1 代码段
 3 }
 4 
 5 这段代码就等价于
 6 … f1() {
 7   synchronized(this){ // 同步锁就是对象本身
 8     // f1 代码段
 9   }
10 }

同样的原则适用于静态(static)函数
比如。

 1static synchronized … f1() {
 2   // f1 代码段
 3 }
 4 
 5 这段代码就等价于
 6static … f1() {
 7   synchronized(Class.forName(…)){ // 同步锁是类定义本身
 8     // f1 代码段
 9   }
10 }

但是,我们要尽量避免这种直接把synchronized加在函数定义上的偷懒做法。因为我们要控制同步粒度。同步的代码段越小越好。synchronized控制的范围越小越好。

 1  package com.synchronization;
 2 class Info{ // 定义信息类
 3  private String name = "李兴华";  // 定义name属性
 4  private String content = "JAVA讲师"  ;  // 定义content属性
 5  private boolean flag = false ; // 设置标志位
 6  
 7  public synchronized void set(String name,String content){
 8   if(!flag){
 9    try{
10     super.wait() ;
11    }catch(InterruptedException e){
12     e.printStackTrace() ;
13    }
14   }
15   this.setName(name) ; // 设置名称
16   try{
17    Thread.sleep(300) ;
18   }catch(InterruptedException e){
19    e.printStackTrace() ;
20   }
21   this.setContent(content) ; // 设置内容
22   flag  = false ; // 改变标志位,表示可以取走
23   super.notify() ;
24  }
25  
26  public synchronized void get(){
27   if(flag){
28    try{
29     super.wait() ;
30    }catch(InterruptedException e){
31     e.printStackTrace() ;
32    }
33   }
34   try{
35    Thread.sleep(300) ;
36   }catch(InterruptedException e){
37    e.printStackTrace() ;
38   }
39   System.out.println(this.getName() +
40    " --> " + this.getContent()) ;
41   flag  = true ; // 改变标志位,表示可以生产
42   super.notify() ;
43  }
44  public void setName(String name){
45   this.name = name ;
46  }
47  public void setContent(String content){
48   this.content = content ;
49  }
50  public String getName(){
51   return this.name ;
52  }
53  public String getContent(){
54   return this.content ;
55  }
56 }
57 
58 class Producer implements Runnable{ // 通过Runnable实现多线程
59  private Info info = null ;  // 保存Info引用
60  public Producer(Info info){
61   this.info = info ;
62  }
63  public void run(){
64   boolean flag = false ; // 定义标记位
65   for(int i=0;i<50;i++){
66    if(flag){
67     this.info.set("李兴华","JAVA讲师") ; // 设置名称
68     flag = false ;
69    }else{
70     this.info.set("mldn","www.mldnjava.cn") ; // 设置名称
71     flag = true ;
72    }
73   }
74  }
75 }
76 
77 class Consumer implements Runnable{
78  private Info info = null ;
79  public Consumer(Info info){
80   this.info = info ;
81  }
82  public void run(){
83   for(int i=0;i<50;i++){
84    this.info.get() ;
85   }
86  }
87 }
88 
89 public class ThreadCaseDemo{
90  public static void main(String args[]){
91   Info info = new Info(); // 实例化Info对象
92   Producer pro = new Producer(info) ; // 生产者
93   Consumer con = new Consumer(info) ; // 消费者
94   new Thread(pro).start() ;
95   new Thread(con).start() ;
96  }
97 }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2015-04-11 10:28  perfect亮  阅读(1540)  评论(0编辑  收藏  举报