锁
锁主要被分为内置锁和Lock体系下的锁,目前这些锁包含:
- 内置锁
- synchronized
- Lock锁
- ReentrantLock
- ReadWriteLock
内置锁
内置锁其实主要就是synchronized(同步代码块),即使最开始的锁也是最长用的锁。
这是一个简单的同步代码块的使用例子
public Thread getDefThread () {
return new Thread(new Runnable() {
@Override
public void run() {
// 获取 锁,离开代码块后就释放锁
synchronized (lock) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = value + 1;
System.out.println(value);
}
}
});
}
synchronized的作用就是保证被保护起来的代码同一时间只有一个线程能够执行里面的逻辑。上面的例子中lock就是内置锁所持有的锁,
而这个锁可能是个对象或者字符串Object lock = new Object()
。
除了上面的用法,synchronized也可以使用另外一种方式
public synchronized void testSync() {
// do some things
}
需要注意的是和第一个例子很明显看到锁对象不同,此时synchronized持有的锁为当前实例对象。
而另外一种情况
public static synchronized void testClassSync() {
// do some things
}
这个时候持有的锁是当前对象对应的class对象
Lock锁
lock锁我们使用的最多的是 ReentrantLock 和 ReadWriteLock两种。
我们先看lock锁的接口内容
public interface Lock {
/**
* 获得锁
*/
void lock();
/**
* 这种方式获得锁,当线程interrupted的时候会抛出InterruptedException异常并释放锁。
*/
void lockInterruptibly() throws InterruptedException;
/**
* 获得锁则返回true,否则返回false
*/
boolean tryLock();
/**
* 设置超时的获取锁的方法
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* 释放锁
*/
void unlock();
/**
* 获得锁的Condition,Condition的作用替代了 Object 监视器
* 用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()
*/
Condition newCondition();
}
ReentrantLock
ReentrantLock的内部维护了一个参数 类Sync(此类继承了AQS)使用其作为逻辑核心,提供两种获取模式 公平锁和非公平锁(默认)
创建
private ReentrantLock lock = new ReentrantLock();
/**
* 非公平策略的RenntrantLock
*/
private ReentrantLock nonfairSync = new ReentrantLock(false);
/**
* 公平策略的RenntrantLock
*/
private ReentrantLock fairSync = new ReentrantLock(true);
lock的其他方法
lock和unlock
主要实现锁的获取和释放,lock获得锁,unlock释放锁
/**
* 锁的简单用法
*/
public Thread getDefThread () {
return new Thread(new Runnable() {
@Override
public void run() {
// 获取锁
lock.lock();
try {
Thread.sleep(1000);
System.out.println("do some things");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
});
}
tryLock
另一种获得锁的方式,尝试获得锁,当获得锁失败的时候返回false,成功获得锁则返回true
/**
* 若当下不能取得lock,thread就会放弃,
*/
public Thread getTryLock () {
return new Thread(new Runnable() {
@Override
public void run() {
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + "得到了锁.");
Thread.sleep(1000);
} catch (InterruptedException e1) {
System.out.println(Thread.currentThread().getName() + "是否获得锁," + lock.isHeldByCurrentThread());
System.out.println(Thread.currentThread().getName() + " interrupted.");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放了锁.");
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + "获取锁失败.");
}
}
});
}
此时获得锁后才会执行后续内容,tryLock同时支持设置超时,可以在一定时间内阻塞lock.tryLock(10, TimeUnit.SECONDS)
public Thread getTryLockTime () {
return new Thread(new Runnable() {
@Override
public void run() {
try {
if (lock.tryLock(10, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread().getName() + "得到了锁.");
Thread.sleep(1000);
} catch (InterruptedException e1) {
System.out.println(Thread.currentThread().getName() + "是否获得锁," + lock.isHeldByCurrentThread());
System.out.println(Thread.currentThread().getName() + " interrupted.");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放了锁.");
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + "获取锁失败.");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
lockInterruptibly
lockInterruptibly是一种比较特殊的申请锁方式,lockInterruptibly将以阻塞方式获取锁,
当无法获得锁的时候方法将会阻塞。同时此方法也会对interrupt做出相应。抛出InterruptedException异常
public Thread getLockInterruptibly () {
return new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lockInterruptibly();
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "得到了锁.");
} catch (InterruptedException e1) {
System.out.println(Thread.currentThread().getName() + "是否获得锁," + lock.isHeldByCurrentThread());
System.out.println(Thread.currentThread().getName() + " interrupted.");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
System.out.println(Thread.currentThread().getName() + "释放了锁.");
lock.unlock();
}
}
}
});
}
执行下面的方法
public void lockInterruptiblyTest() {
Thread t = getLockInterruptibly();
Thread t2 = getLockInterruptibly();
t.setName("线程A");
t2.setName("线程B");
t.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
// t2.interrupt();
}
线程A得到了锁.
线程A释放了锁.
线程B得到了锁.
线程B释放了锁.
ReadWriteLock
ReadWriteLock就是读写锁,可以实现并发的读,非并发的写操作
构造函数
内部维持读锁和写锁,和ReentrantLock一样,支持公平和非公平锁的配置。默认非公平锁
public ReentrantReadWriteLock() {
this(false);
}
/**
*
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
简单的使用
/**
* 锁的简单用法 ——写
*/
public Thread getReadThread () {
return new Thread(new Runnable() {
@Override
public void run() {
Lock readLock = lock.readLock();
readLock.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "得到了读锁");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了读锁.");
}
}
});
}
public Thread getWriteThread () {
return new Thread(new Runnable() {
@Override
public void run() {
Lock writeLock = lock.writeLock();
writeLock.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "得到了写锁");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了写锁.");
}
}
});
}
我们来运行以下方法
/**
* 写不可并发
* 读可以并发
*/
public void tryReadAndWriteTest() {
Thread t3 = getWriteThread();
Thread t4 = getWriteThread();
t3.setName("线程C");
t4.setName("线程D");
t3.start();
Thread t = getReadThread();
Thread t2 = getReadThread();
Thread t5 = getReadThread();
Thread t6 = getReadThread();
t.setName("线程A");
t2.setName("线程B");
t5.setName("线程E");
t6.setName("线程F");
t.start();
t2.start();
t4.start();
t5.start();
t6.start();
}
线程C得到了写锁
线程C释放了写锁.
线程B得到了读锁
线程A得到了读锁
线程A释放了读锁.
线程B释放了读锁.
线程D得到了写锁
线程D释放了写锁.
线程F得到了读锁
线程F释放了读锁.
线程E得到了读锁
线程E释放了读锁.
- 你可以发现当写数据的时候,获取读锁会被阻塞。
- 当正在读的时候,依然可以获得读锁,但是获取写锁会被阻塞。
这也就是说 读可以并发执行、写不可以并发执行、读写不可以并发执行。当然只是一次输出的结果,其实多次输出也可以得到类似内容。
关于公平锁和非公平锁
上面介绍显式锁的时候经常说起公平锁和非公平锁,而且默认创建的都是非公平锁,这是为什么呢?
关于公平锁和非公平锁的区别
- 公平锁:公平的锁上,线程将按照它们发出请求的顺序来获得锁。
- 非公平锁:“非公平锁”在每次尝试获取锁时,都是采用的非公平策略(无视等待队列,直接尝试获取锁,如果锁是空闲的,即可获取状态,则获取锁)
那么为什么默认都是使用非公平锁呢?
原因是恢复一个被挂起的线程与该线程真正开始运行之间存在严重的延迟。此时一个线程完成后,刚好有一个新的线程请求锁那么新的线程很可能在队列前方线程被完全唤醒之前已经完成获得、使用以及释放这个锁的这个流程。这里就涉及到另外一个问题,阻塞所带来的线程上下文切换的开销。
那么什么时候使用公平锁呢?
当持有锁时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁。这个时候,因为处理任务的时间要远远超过切换上下文的时间。“插队”所带来的性能提升会显得微不足道。
内置锁和Lock锁的优缺点
内置锁的问题:无法中断一个正在等待获取锁的线程,无法在请求获取一个锁时无限地等待下去。内置锁必须在获取该锁的代码块中释放,同时无法实现非阻塞结构的加锁规则。
而和内置锁比起来Lock体系下的锁显得更加功能强大,但是功能强大也意味着更加复杂的使用规则,并且使用Lock,必须在finally块中释放锁。否则如果在北保护的代码中抛出了异常,那么这个锁永远都无法释放。这对开发人员编码有了一定要求。
那么要如何选择
与显式锁相比,内置锁仍然具有很大的优势。内置锁简洁紧凑,而且使用见简单。ReentrantLock可以作为一种更高级工具当需要一些高级的功能的时候再去使用ReentrantLock。
总结
本篇主要介绍了内置锁和显式锁的使用,以及两者的比较。