目录
四种锁的介绍
synchronized的缺点
synchronized和Lock的区别
Lock接口
Lock实现类ReentrantLock
ReadWriteLock接口
ReadWriteLock实现类ReentrantReadWriteLock
四种锁的介绍
1.可重入锁
如果锁具备可重入性,则称作为可重入锁。所谓的重入性即在执行对象获得锁之后访问其他同步方法不用再次申请获取锁。
2.可中断锁
可以响应中断的锁称为可中断锁。如果某线程A正在执行锁中的代码,另一线程B正在等待获取锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
3.公平锁
按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁的权力。
4.读写锁
读写锁将对一个资源的访问分成了两个锁,一个读锁和一个写锁。正是因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
synchronized的缺点
synchronized是Java中一个关键字,其是Java语言内置的特性。在前面的文章中,我们可了解到synchronized的详情。那么为何可以通过synchronized来实现同步访问了,为什么还需要提供Lock呢?从Java 5之后,在java.util.concurrent.locks包下提供了另一种方法来实现同步访问,那就是Lock。当然是因为synchronized不够完美而提出提供更多功能的Lock接口。那么synchronized的缺点是啥呢?
(1)同一时刻不管是读还是写都只能有一个线程对共享资源操作,其他线程只能等待。
(2)不能响应中断。
(3)锁的释放由虚拟机来完成,无法手动去释放锁,有可能获取到锁的线程阻塞之后其他线程会一直等待,性能不高。
synchronized和Lock的区别
下面表格是两者的一些类别对比:
类别 |
synchronized |
Lock |
存在层次 |
Java的关键字,在jvm层面上 |
是一个接口 |
锁的释放 |
1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 |
在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 |
假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 |
分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待 |
锁状态 |
无法判断 |
可以判断 |
锁类型 |
可重入 不可中断 非公平 |
可重入 可判断 可公平(两者皆可) |
性能 |
少量同步 |
大量同步 |
总结:
(1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现的。
(2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。
(3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能响应中断。
(4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
(5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
Lock接口
下面来看看Lock接口的源码:
public interface Lock {
/** * Acquires the lock. */
void lock();
void lockInterruptibly() throws InterruptedException;
/** * Acquires the lock only if it is free at the time of invocation. */
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/** * Releases the lock. */
void unlock();
Condition newCondition();
}
lock()方法:获取锁,如果锁已被其他线程获取,则进行等待。因为Lock必须主动去释放锁,并且在发生异常时,不会自动释放锁,因此一般进行异常捕获,并且将释放锁的操作放在finally块中进行,以保证锁一定被释放,防止死锁的发生。
tryLock()方法:尝试获取锁,其有返回值且返回的是boolean类型值,如果获取成功则返回true,如果获取失败则返回false,在拿不到锁时不会一直在那等待。
tryLock(long time,TimeUnit unit)方法:其和tryLock()方法类似,只不过其给定参数,在拿不到锁会等待一定时间,如果在一定时间内还拿不到锁就返回false,反之返回true。 此方法声明会抛出InterruptedException异常。
unlock()方法:释放锁,使用Lock时一般需要在finally块中释放锁。
lockInterruptibly()方法:如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。其声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者调用lockInterruptibly()的方法外声明抛出InterruptedException。线程调用interrupt()方法能够中断线程的等待过程,当然获取锁的线程无法使用interrupt()方法进行中断,interrupt()方法不能中断正在运行过程的线程,只能中断阻塞过程中的线程。
Lock实现类ReentrantLock
ReentrantLock是“可重入锁”的意思,ReentrantLock是实现Lock接口的唯一类,并且ReentrantLock提供了更多的方法。
package lockTest;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
//全局变量,如果是局部变量那么线程获取的是两个不同的锁
private Lock lock=new ReentrantLock();
public void method(Thread thread) {
/*
lock.lock();//lock()方法获取锁
try {
System.out.println("线程名"+thread.getName() + "获得了锁");
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println("线程名"+thread.getName() + "释放了锁");
}*/
/*
//tryLock()方法尝试获取锁
if(lock.tryLock()) {
try {
//thread.sleep(20);
System.out.println("线程名"+thread.getName() + "获得了锁");
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println("线程名"+thread.getName() + "释放了锁");
}
}else {
System.out.println("我是"+Thread.currentThread().getName()+"有人占着锁,我就不要啦");
}*/
/*
*TimeUnit.DAYS //天
*TimeUnit.HOURS //小时
*TimeUnit.MINUTES //分钟
*TimeUnit.SECONDS //秒
*TimeUnit.MILLISECONDS //毫秒
*TimeUnit.NANOSECONDS //毫微秒
*TimeUnit.MICROSECONDS //微秒
* */
try {
//使用参数获取锁不成功等待两秒尝试
//使用此方法应该注意三点
//第一:尝试获取锁不成功等待时间需大于sleep时间,否则没有获取Lock的直接释放锁会造成异常
//第二:调用sleep()方法后线程就进入阻塞状态
//第三:鉴于网上一些代码实现方式是有毛病的,让我们特别注意其在拿不到锁时不会一直在那等待。
if(lock.tryLock(2000,TimeUnit.MILLISECONDS)) {
System.out.println("线程名"+thread.getName() + "获得了锁");
//Thread.sleep(1000);
System.out.println("线程名"+thread.getName() + "在玩");
}else {
System.out.println("我是"+Thread.currentThread().getName()+"有人占着锁,我就不要啦");
}
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println("线程名"+thread.getName() + "释放了锁");
}
}
public static void main(String[] args) {
LockTest locktest=new LockTest();
new Thread() {
public void run() {
//Thread.currentThread()获取当前线程的引用,this是获取当前对象,为线程类
locktest.method(Thread.currentThread());
}
}.start();
new Thread() {
public void run() {
//Thread.currentThread()获取当前线程的引用,this是获取当前对象,为线程类
locktest.method(Thread.currentThread());
}
}.start();
}
}
package lockInterruptiblyTest;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockInterruptiblyTest {
private Lock lock=new ReentrantLock();
public void method(Thread thread) throws InterruptedException {
lock.lockInterruptibly();
try {
System.out.println("线程名"+thread.getName() + "获得了锁");
Thread.sleep(2000);
System.out.println("线程名"+thread.getName() + "在玩");
}catch(Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
System.out.println("线程名"+thread.getName() + "释放了锁");
}
}
public static void main(String[] args) {
LockInterruptiblyTest a=new LockInterruptiblyTest();
//线程1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
a.method(Thread.currentThread());
}catch(Exception e) {
System.out.println("我是"+Thread.currentThread().getName()+",我被中断拉");
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
a.method(Thread.currentThread());
}catch(Exception e) {
System.out.println("我是"+Thread.currentThread().getName()+",我被中断拉");
}
}
}, "t2");
t1.start();
t2.start();
t2.interrupt();//中断
}
}
ReadWriteLock接口
下面是ReadWriteLock接口的源码:
public interface ReadWriteLock{
Lock readLock();
Lock writeLock();
}
其定义了两个方法,一个用来获取读锁,一个用来获取写锁。下面的ReentrantReadWriteLock类实现了ReadWriteLock接口。
ReadWriteLock实现类ReentrantReadWriteLock
ReentrantReadWriteLock类提供了最主要的两个方法:readLock()和writeLock()用来获取读锁和写锁。下面通过使用读写锁实现缓存系统来了解:
public class CacheSystem {
private Map<String, Object> cache = new HashMap<String,Object>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public Object getData(String key){
//先从缓存中去取数据,先加上读锁
rwl.readLock().lock();
Object obj = null;
try{
obj = cache.get(key);
//如果缓存数据为空
if(obj == null){
//先解除读锁,在上写锁(必须先解除读锁才能成功上写锁因为读写锁互斥)
rwl.readLock().unlock();
rwl.writeLock().lock();
try{
if(obj == null){//避免后面的线程不会重读写
obj = new String("obj is get from db");
cache.put(key, obj);
}
}finally{
//先上读锁,然后再解除写锁(这样可以成功完成,在解除写锁前获得读锁,写锁被降级--这翻译的api上的)
rwl.readLock().lock();
rwl.writeLock().unlock();//解除写锁,读锁仍然持有
}
}
}finally{
//解除读锁
rwl.readLock().unlock();
}
return obj;
}
}
感谢参考了文章资料的作者,很早之前写的文章,忘记了参考文章资料的地址了,抱歉!!!!!!