从Java5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock。讨论lock前先了解下synchronized
一:synchronized
synchronized是java中的一个关键字,也就是说是Java语言内置的特性,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁
释放的两种情况
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有,在次期间该对象不能被释放或者操作。
2) 线程发生异常,JVM自动会释放锁,无需用户处理。
如果一个对象被加了synchronized,那么不管线程是读或者写操作都不可以操作。我们来看一段代码加深下synchronized的理解
public class SynchronizedTest {
public static void main(String[] args) {
Thread a = new ThreadRunC();
a.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是主线程 不加同步"+C.C);
synchronized (C.C) {
System.out.println("我是主线程:" + C.C);
}
}
}
class ThreadRunC extends Thread {
public void run() {
synchronized (C.C) {
System.out.println("我要开始执行任务C。。。。" + Thread.currentThread().getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我在执行任务结束了C。。。。" + Thread.currentThread().getName());
}
}
}
class C {
static Integer C = new Integer(1);
}
结果:
我要开始执行任务C。。。。Thread-0
我是主线程 不加同步1
我在执行任务结束了C。。。。Thread-0
我是主线程:1
有此可见C.C对象在进入子线程后休眠了5s,那么主线程的synchronized (C.C)一直处于等待状态,直到子线程结束,它才执行。
二:Lock
Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock(jdk1.5以后)是一个类(实际是接口),通过这个类可以实现同步访问,它和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
public interface Lock {
void lock(); //获取锁。
void lockInterruptibly() throws InterruptedException; //如果当前线程未被中断,则获取锁。
boolean tryLock(); // 仅在调用时锁为空闲状态才获取该锁。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
void unlock();// 释放锁。
Condition newCondition(); //返回绑定到此 Lock 实例的新 Condition 实例。
}
1:ReentrantLock
ReentrantLock,意思是“可重入锁”,ReentrantLock由最近成功获取锁,还没有释放的线程所拥有,当锁被另一个线程拥有时,调用lock的线程可以成功获取锁。如果锁已经被当前线程拥有,当前线程会立即返回。此类的构造方法提供一个可选的公平参数。默认是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平与非公平有何区别,所谓公平就是严格按照FIFO顺序获取锁,非公平安全由程序员自己设计,比如可以按优先级,也可以按运行次数等规则来选择。
公平加锁代码
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
1、首先判断锁有没有被持有,如果被持有,就判断持有锁的线程是不是当前线程,如果不是就啥也不做,返回获取失败,如果是就增加重入数,返回成功获取;
2、如果锁没有被任何线程持有(c==0),首先判断当前结点前面是否还有线程在排除等待锁,如果有,直接返回获取失败,否则将锁持有数设为acquires,一般为1,然后设置锁的拥有者为当前线程,成功获取。
非公平加锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
代码几乎一模一样,唯一不同的是在知道锁持有数为0时,直接将当前线程设置为锁的持有者,这一点和公平版本的tryAcquire是有区别的,也就是说非公平机制采用的是抢占式模型。
2.ReadWriteLock
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading.
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing.
*/
Lock writeLock();
}
一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。下面的ReentrantReadWriteLock实现了ReadWriteLock接口。
3 ReentrantReadWriteLock
ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。实现了ReadWriteLock接口
测试代码
public class LockRWDemo {
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
Thread a = new ThreadRunW();
Thread b = new ThreadRunW();
a.start();
b.start();
}
}
class ThreadRunR extends Thread {
public void run() {
LockRWDemo.rwl.readLock().lock();;
try {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(Thread.currentThread().getName()+"正在进行读操作");
}
System.out.println(Thread.currentThread().getName()+"读操作完毕");
} finally {
LockRWDemo.rwl.readLock().unlock();
}
}
}
读的操作是两个线程交替的进行的,这样就大大提升了读操作的效率。
不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
class ThreadRunW extends Thread {
public void run() {
LockRWDemo.rwl.writeLock().lock();;
try {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(Thread.currentThread().getName()+"正在进行写操作");
}
System.out.println(Thread.currentThread().getName()+"写操作完毕");
} finally {
LockRWDemo.rwl.writeLock().unlock();
}
}
}
此代码的结果是只能等一个线程结束了才能执行另外一个。
三:Lock和synchronized区别和选择
总结来说,Lock和synchronized有以下几点不同:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。