java锁:synchronized、ReadWriteLock、ReentrantReadWriteLock*

原创
2016/12/28 15:13
阅读数 200

一、先读这篇文章,了解synchronized:Java线程同步:synchronized锁住的是代码还是对象

  1. synchronizied,默认是synchronized(this),括号里的内容可以看成是锁。
  2. 把锁当成对象看待,在类C中加锁的代码块A,能锁住代码块A的锁有很多,new出C类的对象c1、c2,默认就是synchronized(c1){A}、synchronized(c2){A},在对象c1中,获得了c1的锁才能执行代码。在对象c2中,获得了c2的锁才能执行代码。相同的代码块A,在不同的对象c1、c2中,是可以同时执行的,但是在对象c1中,A是不能执行的(c2也同理)。
  3. 在类C中加锁的代码块A,synchronized(C.class){A},这时锁是C的class对象,相当于在一个jvm中,只有一个C的class对象,所以这时候就是全局锁了,在整个jvm中,代码A只能有一个并发。
  4. 加锁的代码是会影响并发的,所以加锁的内容能缩小就尽量缩小。
  5. 如果在一个对象中,有多个synchronized代码块A,B,C,则A现在正被锁着,B和C也会被同时锁住。

二、synchronized加锁造成的问题
对象的方法中一旦加入synchronized修饰,则这个对象任何时刻只能有一个线程访问synchronized修饰的方法。假设有个数据对象拥有写方法与读方法,多线程环境中要想保证数据的安全,需对该对象的读写方法都要加入 synchronized同步块。这样任何线程在写入时,其它线程无法读取与改变数据;如果有线程在读取时,其他线程也无法读取或写入。这种方式在写入操作远大于读操作时,问题不大,而当读取远远大于写入时,会造成性能瓶颈,因为此种情况下读取操作是可以同时进行的,而加锁操作限制了数据的并发读取。这就引出了ReadWriteLock,读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
但是ReadWriteLock是个接口,ReentrantReadWriteLock是他的实现类,ReentrantReadWriteLock特性如下:

  • ReentrantReadWriteLock和synchronized一样,不同的对象是不同的锁,一个对象产生的锁,只能本身的对象。
  • 重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。
  • WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能。
  • ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。

参考:ReentrantReadWriteLock读写锁的使用
三、ReentrantReadWriteLock实例
ReentrantReadWriteLock实现了ReadWriteLock,使用ReentrantReadWriteLock,只需要在对象O(或者某个数据结构)中定义ReentrantReadWriteLock对象即可,然后调用lock或者unlock,即可对对象O实现加锁操作。

package com.sf.log_gen;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockTest {

  public static void main(String[] args) {
    Count count = new Count();
    new Thread(new Write(count)).start();
    // 因为是不同的对象,所以这里立马就能获得ReadLock
    System.out.println(new Count().get());
    // 针对同一对象to,因为子线程先获得了对象to的WriteLock(也有可能主线程先获得,主线程最好sleep(100L)),
    // 所以这里ReadLock就需要等WriteLock释放才能获得
    System.out.println(count.get());
  }
}


class Write implements Runnable {
  Count count = null;

  public Write(Count count) {
    this.count = count;
  }

  public void run() {// 在这个线程中先获得了writeLock
    this.count.lock();
    this.count.add();
    this.count.unlock();
  }
}


class Count {
  private ReadWriteLock rwl = new ReentrantReadWriteLock();
  int num = 0;

  public void add() {
    try {
      Thread.sleep(4000L);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    num++;
  }

  public int get() {// get方法如果不加锁,则所有线程可以直接读取
    this.rwl.readLock().lock();
    int ret = num;
    this.rwl.readLock().unlock();
    return ret;
  }

  public void lock() {
    this.rwl.writeLock().lock();
  }

  public void unlock() {
    this.rwl.writeLock().unlock();
  }
}

四、悲观锁乐观锁 上面的锁都是悲观锁
乐观锁即我先对num操作,操作完了之后,在判断num是否有变化,没变化,则说明没有别的线程在对num操作,那我就可以把我操作后的值赋值给num了。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部