当对数据修改时,如果两个线程同时去修改同一条数据,这样产生的结果就不是我们预期的结果。这时候就需要对修改操作进行加锁,让jvm里同一时刻只能有一个线程能够执行修改方法。
下面是一个未加锁的修改方法:
public void update(Entry entry){
dao.update(entry);
}
现在讨论下传统的加锁方法。我们知道每一个对象都隐含了一个锁,那就是对象本身。我们可以在方法体上加上同步字段synchronized
public synchronized void update(Entry entry){
dao.update(entry);
}
但如果方法里代码很多,那么加在方法上会锁住很多代码,我们可以使用同步块
public void update(Entry entry){
dobefore();
synchronized(this){
dao.update(entry);
}
doend();
}
而需要注意的是如果一个类中存在多个同步方法,那么所有同步方法的锁都是对象本身,也就是说当执行update的时候,别的线程不仅不能执行update连类中别的同步方法也不能使用。当然也可以使用一点技巧去规避这个问题,比如使用其他锁。
我们这篇博客说得不是上面的方法,而是另外一个位于java.util.concurrent.locks包下的ReentrantLock。
官方是这么说的:
一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。
此类的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。不过要注意的是,公平锁不能保证线程调度的公平性。因此,使用公平锁的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁时。还要注意的是,未定时的 tryLock 方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁是可用的,此方法就可以获得成功。
使用它也很简单,你可以用如下结构来使用他:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
常用的方法就如上面所说的一样,lock和unlock
lock
public void lock()
获取锁。
如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1。
如果当前线程已经保持该锁,则将保持计数加 1,并且该方法立即返回。
如果该锁被另一个线程保持,则出于线程调度的目的,禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态,此时锁保持计数被设置为 1。
指定者:
接口 Lock 中的
lock
unlock
public void unlock()
试图释放此锁。
如果当前线程是此锁所有者,则将保持计数减 1。如果保持计数现在为 0,则释放该锁。如果当前线程不是此锁的持有者,则抛出 IllegalMonitorStateException。
指定者:
接口 Lock 中的
unlock
抛出:
IllegalMonitorStateException - 如果当前线程没有保持此锁
当然他还有别的很多方法,需要的话可以自己去看看。
对比synchronized这个锁是独立的,在一个类中多个同步块之间都是独立的,互不影响。最后说一句,因为同步块会让一段代码同一时刻只能有一个线程使用,多线程同时访问,一个使用其他都是等待状态,那么就存在一个性能问题。如果理解原子性,又那么牛X,利用原子性写出避免同步的免锁代码,什么synchronized啊,ReentrantLock啊,都是浮云。