您的位置:首页 > 编程语言 > Java开发

java锁:synchronized、ReadWriteLock、ReentrantReadWriteLock*

2016-12-28 15:13 429 查看
一、先读这篇文章,了解synchronized:Java线程同步:synchronized锁住的是代码还是对象

synchronizied,默认是synchronized(this),括号里的内容可以看成是锁。

把锁当成对象看待,在类C中加锁的代码块A,能锁住代码块A的锁有很多,new出C类的对象c1、c2,默认就是synchronized(c1){A}、synchronized(c2){A},在对象c1中,获得了c1的锁才能执行代码。在对象c2中,获得了c2的锁才能执行代码。相同的代码块A,在不同的对象c1、c2中,是可以同时执行的,但是在对象c1中,A是不能执行的(c2也同理)。

在类C中加锁的代码块A,synchronized(C.class){A},这时锁是C的class对象,相当于在一个jvm中,只有一个C的class对象,所以这时候就是全局锁了,在整个jvm中,代码A只能有一个并发。

加锁的代码是会影响并发的,所以加锁的内容能缩小就尽量缩小。

如果在一个对象中,有多个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了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐