Android线程同步

来源:互联网 发布:桂林绿源网络 编辑:程序博客网 时间:2024/06/14 17:12

1.背景

多个线程都在操作同一数据导致数据不一致,所以我们用同步机制来解决这些问题.

同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。我们可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。  

同步锁不是加在共享资源上,而是加在访问共享资源的代码段上。同步锁本身也一定是多个线程之间的共享对象。

原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源.根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括long和double, 因为JVM看到的基本存储单位是32位,而long 和double都要用64位来表示。所以无法在一个时钟周期内完成。

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

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

2.线程同步机制的实现

①同步代码块:synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据。

②同步方法:public synchronized 数据返回类型方法名(){}.

通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征:  

1.该类的对象可以被多个线程安全的访问。  

2.每个线程调用该对象的任意方法之后,都将得到正确的结果。

3.每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。

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

当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。

1.假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。

2.假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。 (一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁 如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中)

3.线程通讯

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

Java.lang.object 里的三个方法:

wait():导致当前线程等待,直到其他线程调用同步监视器的notify方法或notifyAll方法来唤醒该线程

wait(mills):等待指定时间后自动苏醒,调用wait方法的当前线程会释放该同步监视器的锁定,可以不用notify或notifyAll方法把它唤醒。

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

notifyAll():唤醒在同步监视器上等待的所有的线程。只用当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。

释放对象的锁:

1.执行完同步代码块就会释放对象的锁

2.在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放

3.在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。

死锁:

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

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

4.线程同步的特征

1.如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制)

2.每个对象都有唯一的同步锁

3.在静态方法前面可以使用synchronized修饰符。

4.当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。

5.Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。

5.同步锁example

public static final Object lock1 = new Object();… f1() {synchronized(lock1){ // lock1 是公用同步锁  // 代码段 A  // 访问共享资源 resource1  // 需要同步}  //不一定要把同步锁声明为static或者public,但是一定要保证相关的同步代码之间,一定要使用同一个同步锁.使用final关键字,这就是为了保证lock1的Object Reference在整个系统运行过程中都保持不变

6.同步粒度

… synchronized … f1() {  // f1 代码段}这段代码就等价于… f1() {  synchronized(this){ // 同步锁就是对象本身    // f1 代码段  }}同样的原则适用于静态(static)函数比如。… static synchronized … f1() {  // f1 代码段}这段代码就等价于…static … f1() {  synchronized(Class.forName(…)){ // 同步锁是类定义本身    // f1 代码段  }} //我们要控制同步粒度。同步的代码段越小越好。synchronized控制的范围越小越好。

0 0