也来说说Java中的锁
一,什么是java中的锁
1,从java语法上来说,java中的锁,指的就是java给我们提供的Lock接口以及相关的实现类。
public interface Lock ,通常我们这样来创建锁对象:
Lock lock = new ReentrantLock();
2,从架构上来说,锁是一种同步机制;
3,从功能上来说,锁是用来控制多个线程访问共享资源的方式,防止多个线程同时访问共享资源。
二,从JVM的角度来看锁
说起锁,就要说一下synchronized关键字。锁是JDK5开始才引入的一个功能,而synchronized
关键字在JDK5之前就已经存在了,并且在没有锁的日子里,java就靠synchronized关键字来实现锁
的功能的。锁与synchronized关键字也相似的功能,区别就在于锁是显示的,而synchronized关键
字是隐式的。
1,锁的内存语义
锁是一种同步机制,锁可以让临界区互斥的执行,在多线程环境下实现强同步,并且锁L在释放
时,释放锁L的A线程会通知获得锁L的B线程。这就是锁的内存语义。
2,锁的释放和获取的内存语义
第一点:锁的释放的内存语义是:当线程A释放锁时,Java内存模型会把本地内存中的共享变量
的值刷新到主内存中。
第二点:锁的获取的内存语义是:当线程A获取锁时,Java内存模型会把本地内存中的共享变量
的值置为无效,然后去主内存中读取新值。
在这里补充说明一下,锁的释放和volatile关键字的写操作具有相同的内存语义,锁的获取和
volatile关键字的读具有相同的内存语义。并且从实现上来说,Java中锁机制的实现,依赖于
volatile关键字。
读者请思考:锁与volatile的不同点?
3,锁的内存语义的实现
锁有很多种实现,这里我们仅仅以最常用的可重入锁ReentrantLock为例来说明,可重入锁
ReentrantLock的内存语义的实现是这样的:调用Lock方法获取锁,调用unlock方法释放锁。可重入
锁的实现依赖于AQS(AbstractQueueSynchronizer)同步器框架。而AQS实现的关键就是volatile关
键字,因此我们通常说,锁的内存语义的实现,依赖于volatile关键字。
三,从功能实现和使用的角度来看锁
Java支持多线程,也就是多个线程可以同时访问同一个变量或者对象,由于每个对象都拥有这个
变量的拷贝,所以在程序的执行过程中,某个线程看到的变量的值不一定是最新的,因此就产生了线
程同步的问题。之所以每个线程都拥有一份拷贝,是为了加快程序的运行速度,这是很多多核处理器
的共性。
1,锁的使用方法
锁的使用方法比较简单,通过下面一段代码,就可以熟悉锁是如何使用的。
package com.spider.java;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* @author yangcq
* @desctiption Java中的锁
*
*/
public class LockLearning {
// 定义一个全局变量,账户余额
private static int account_balance = 1000000000;
public static void main(String[] args) {
// 创建可重入锁对象lock
Lock lock = new ReentrantLock();
// 在执行一段代码之前获取锁
lock.lock();
// 执行代码,修改余额的值,这里可以是我们项目中任何一个需要加锁的操作
updateAccountBalance(account_balance);
// 在执行一段代码之后释放锁
lock.unlock();
}
private static int updateAccountBalance(Integer account_balance){
return account_balance - 1000;
}
}
下面我们来看一下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():获取锁,调用该方法后,当前线程马上获得锁,当锁获得后,从该方法返回。
lockInterruptibly():获取锁(支持中断获取锁)
tryLock():获取锁(非阻塞的获取锁,调用该方法后,会立即返回)
tryLock(long time, TimeUnit unit):获取锁(支持超时获取锁)
unlock():释放锁
newCondition():获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,
才能调用该组件的wait方法,而调用后,当前线程将被释放锁。
源码中注释很详细,大家可以细细品味一番:
/**
* Returns a new {@link Condition} instance that is bound to this
* {@code Lock} instance.
*
* <p>Before waiting on the condition the lock must be held by the
* current thread.
* A call to {@link Condition#await()} will atomically release the lock
* before waiting and re-acquire the lock before the wait returns.
*
* <p><b>Implementation Considerations</b>
*
* <p>The exact operation of the {@link Condition} instance depends on
* the {@code Lock} implementation and must be documented by that
* implementation.
*
* @return A new {@link Condition} instance for this {@code Lock} instance
* @throws UnsupportedOperationException if this {@code Lock}
* implementation does not support conditions
*/
2,锁与synchronized关键字的比较
说到锁,就不得不提锁的始祖synchronized关键字,首先来看一下synchronized关键字的使用。
package com.spider.java;
import java.util.logging.Logger;
/**
*
* @author yangcq
* @description synchronized关键字的使用方法
*
*/
public class SynchronizedLearning {
static Logger logger = Logger.getAnonymousLogger();
public static void main(String[] args) {
/**
* synchronized关键字实现同步有2种方式,同步方法和同步块
*/
// 同步块
synchronized(SynchronizedLearning.class){
logger.info("我是同步块...");
}
synchronizedMethod();
}
// 同步方法
public static synchronized void synchronizedMethod(){
logger.info("我是同步方法...");
}
}
总结一下,锁与synchronized关键字都可以实现锁的功能,由于锁是诞生在
synchronized关键字之后,所以锁更具有先进性,它具有一些synchronized
关键字不具有的功能,主要有以下几个方面:
支持非阻塞的获取锁;
支持中断的获取锁;
支持超时获取锁;
四,AQS同步器框架(AbstractQueueSynchronizer)
锁的实现,依赖的就是AQS,Lock接口的实现基本上都是通过聚合了一个同步
器的子类来完成线程访问控制的。AQS又叫队列同步器,是用来构建锁或者其他同
步组件的基础框架。AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队
列来完成资源获取线程的排队工作。
AQS的主要使用方法是继承,子类通过继承同步器AQS,然后实现它的抽象方法
来管理同步状态。
队列同步器AQS是锁的实现的关键,在锁的实现中聚合队列同步器,利用队列同
步器实现锁的语义。可以这样理解二者之间的关系:锁是面向使用者的,同步器是面
向锁的实现类。
1,同步框架AQS的使用说明
首先自定义一个锁,独占锁,也就是说在同一时刻只有一个线程可以获得锁。
package com.spider.java;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
*
* @author yangcq
* @description 自定义锁 - 独占锁UserDefineLock,也就是在同一时刻,只有一个线程可以获得锁
*
*/
public class UserDefineLock implements Lock {
// 使用队列同步器AQS以后,我们只需要将锁的操作代理到自定义的同步器上就可以了
private final SynchronizerDefine synchronizerDefine = new SynchronizerDefine();
@Override
public void lock() {
synchronizerDefine.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
synchronizerDefine.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return synchronizerDefine.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return synchronizerDefine.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
synchronizerDefine.release(1);
}
@Override
public Condition newCondition() {
return synchronizerDefine.newCondition();
}
public boolean hasQueueThreads(){
return synchronizerDefine.hasQueuedThreads();
}
public boolean isLocked(){
return synchronizerDefine.isHeldExclusively();
}
// 静态内部类,自定义同步器
private static class SynchronizerDefine extends AbstractQueuedSynchronizer{
/**
*
*/
private static final long serialVersionUID = -5572420543429760541L;
// 判断当前锁是否处于占用状态
protected boolean isHeldExclusively(){
return getState() == 1;
}
// 当状态为0时,获取锁
public boolean tryAcquire(int acquires){
if(compareAndSetState(0,1)){
// 官方注释:Sets the thread that currently owns exclusive access.
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 释放锁之后,将状态设置为0
protected boolean tryRelease(int releases){
if(getState() == 0){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 返回一个Condition,每个Condition都包含一个condition实例
Condition newCondition(){
return new ConditionObject();
}
}
}
然后编写一个测试类,启动10个线程,竞争一个资源,每1.5秒打印一行信息
package com.spider.java;
import java.util.concurrent.locks.Lock;
import java.util.logging.Logger;
/**
*
* @author yangcq
* @description 队列同步器AbstractQueuedSynchronizer
* @description public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
*
*/
public class AbstarctQueueSynchronizerLearning {
// 打印logger日志
static Logger logger = Logger.getAnonymousLogger();
// 主方法
public static void main(String[] args){
test();
}
public static void test(){
final Lock lock = new UserDefineLock();
class ProductThread extends Thread{
public void run(){
while(true){
lock.lock();
try{
sleep(1500);
logger.info(Thread.currentThread().getName());
sleep(1500);
}
catch(Exception e){
lock.unlock();
}
finally{
lock.unlock();
}
}
}
}
// 启动5个线程
for(int i=0;i<10;i++){
ProductThread productThread = new ProductThread();
productThread.setDaemon(true);
productThread.start();
}
// 每隔1.5秒 打印 -----------
for(int i=0;i<10;i++){
try {
ProductThread.sleep(1500);
logger.info("-----------");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
控制台输出信息(由于竞争关系,控制台输出是随机的,每次运行结果不同):
2016-7-31 1:44:04 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:04 com.spider.java.AbstarctQueueSynchronizerLearning$1ProductThread run
信息: Thread-1
2016-7-31 1:44:06 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:07 com.spider.java.AbstarctQueueSynchronizerLearning$1ProductThread run
信息: Thread-1
2016-7-31 1:44:07 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:09 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:10 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:10 com.spider.java.AbstarctQueueSynchronizerLearning$1ProductThread run
信息: Thread-1
2016-7-31 1:44:12 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:13 com.spider.java.AbstarctQueueSynchronizerLearning$1ProductThread run
信息: Thread-3
2016-7-31 1:44:13 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:15 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:16 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:44:16 com.spider.java.AbstarctQueueSynchronizerLearning$1ProductThread run
信息: Thread-3
2016-7-31 1:44:18 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
五,可重入锁 java.util.concurrent.locks.ReentrantLock
1,什么是可重入锁
可重入锁,就是支持重新进入的锁,也就是支持同一个线程重复的获取同一个锁。可
重入锁与我们之前定义的独占锁,有什么区别呢,一个明显的区别就是,如果我们在一个
线程A获取锁之后,再次调用lock.lock()方法,此时,线程A会被阻塞,线程A自己把自己
阻塞了,如果这样修改一下,上面程序的运行结果就会变成这样:
2016-7-31 1:58:50 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:51 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:53 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:54 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:56 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:57 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:58:59 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:59:00 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:59:02 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2016-7-31 1:59:03 com.spider.java.AbstarctQueueSynchronizerLearning test
信息: -----------
2,可重入锁是如何实现的
可重入锁java.util.concurrent.locks.ReentrantLock中,有一个方法用来判断当前线程
是否是获取锁的线程,也就是说如果是一个已经获取锁的线程A,再次调用lock.lock()方法时,
可重入锁会将同步状态值加增加,然后返回true,表示获取锁成功。源代码如下:
/**
* Performs non-fair tryLock. tryAcquire is
* implemented in subclasses, but both need nonfair
* try for trylock method.
*/
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;
}
这样处理以后,释放锁时,相应的也要改变处理逻辑:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
3,可重入锁的公平与非公平竞争实现
可重入锁支持以公平的方式,还是非公平的方式获取锁。设置很简单,通过下面的
代码,在创建锁对象时进行设置:
Lock lock = new ReentrantLock(true); // 以公平的方式获取锁
Lock lock = new ReentrantLock(false); // 以非公平的方式获取锁
源码如下:
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = (fair)? new FairSync() : new NonfairSync();
}
这2种获取锁的方式有什么区别呢?区别就在于,以公平方式获取锁时,会判断当前线程是
不是第一个请求获取该锁的线程。如果是第一个请求获取该锁的线程,则获取锁成功;否则
获取锁失败。
/**
* Sync object for fair locks 以公平的方式获取锁
*/
final static class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 区别就在于这里,以公平方式获取锁时,会判断当前线程是不是
// 第一个请求获取该锁的线程。
if (isFirst(current) && 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;
}
}
/**
* Sync object for non-fair locks 以非公平的方式获取锁
*/
final static class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// 非公平方式的方法实现nonfairTryAcquire
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;
}
六,读写锁 java.util.concurrent.locks.ReentrantReadWriteLock;
1,什么是读写锁
读写锁这样一种锁,它允许多个线程在同一时刻同时访问某个资源。读写锁维护
了一对锁,一个读锁和一个写锁,其实原理就是读写分离,因为在以读为主的场景下,
大部分时间都是读取,我们都知道,多个读线程同时访问,始终能保持同步状态。所以
此时不加锁可以有效的提高程序的执行速度。
2,读写锁的实现原理
读写锁的原理,其实就是维护了2个锁,一个读锁和一个写锁。在读操作时获取读
锁,写操作时获取写锁。当写锁被获取到时,后面的读写请求都被阻塞,写锁释放后,
后面的读写操作继续执行。
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable
/** Inner class providing readlock 读锁(内部类)*/
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock 写锁(内部类)*/
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics 队列同步器AQS实现类*/
private final Sync sync;
读锁和写锁,都是ReentrantReadWriteLock的内部类,这2个锁,都是实现了Lock接口的
实现类。
public static class ReadLock implements Lock, java.io.Serializable // 读锁
public static class WriteLock implements Lock, java.io.Serializable // 写锁
七,Condition接口
1,Condition的作用
Condition接口提供了类似Object的监视器方法,与锁配合可以实现等待/通知模式。我们
都知道,任意一个Java对象,都具有下面这几个方法:
wait(); // 等待
wait(long timeout); // 超时等待
notify(); // 唤醒
notifyAll(); // 唤醒所有
这些方法我们很少用到,但是Java都提供给我们了。这些方法有什么作用呢?其实这些方法都
是Object对象的监视器,与synchronized关键字配合使用,可以实现等待/通知模式。
Condition接口也提供了类似的一些方法,源码如下:
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
Condition接口与Lock配合,可以实现等待/通知模式。看到这里,大家可能有些疑惑了,
Object监视器 + synchronized关键字
Condition监视器 + Lock
这不就是等待/通知模式的升级版吗!
2,Condition的使用
package com.spider.java;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
/**
*
* @author yangcq
* @description Condition接口 + Lock 实现等待/通知模式
* @description 测试类 ConditionLearningTest
*
*/
public class ConditionLearning {
static Logger logger = Logger.getAnonymousLogger(); // 日志
Lock lock = new ReentrantLock();
Condition condition_get = lock.newCondition();
// 定义一个Map
Map<String,String> map = new HashMap<String,String>();
// 取值 -- 如果值为空,则等待...
public void conditionWait() throws InterruptedException{
map.put("yangcq", "");
lock.lock();
try{
while("".equals(map.get("yangcq"))){
logger.info("key 'yangcq' 为空,不能取值...");
condition_get.await();
}
logger.info("key 'yangcq' 的值为:" + map.get("yangcq"));
}
finally{
lock.unlock();
}
}
// 赋值 -- 赋值以后,有值了,唤醒
public void conditionSignal() throws InterruptedException{
map.put("yangcq", "1988");
lock.lock();
try{
if("".equals(map.get("yangcq"))){
logger.info("key 'yangcq' 为空,不能让其他方法从这里取值...");
}
else{
logger.info("key 'yangcq' 有值,可以唤醒取值的方法");
// 唤醒取值线程(消费者线程)
condition_get.signal();
}
}
finally{
lock.unlock();
}
}
}
测试类:ConditionLearningTest.java
package com.spider.java;
/**
*
* @author yangcq
* @description Condition接口 + Lock 实现等待/通知模式测试
*
*/
public class ConditionLearningTest {
// 创建ConditionLearning对象
static ConditionLearning conditionLearning = new ConditionLearning();
public static void main(String[] args) {
getValue(); // 初始值为空,消费者线程等待...
putValue(); // 生产者赋值以后,有值了,通知消费者线程,然后消费者线程唤醒
}
@SuppressWarnings("static-access")
public static void getValue(){
// 消费者线程
class ConsumerThread extends Thread{
public void run(){
try {
conditionLearning.conditionWait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ConsumerThread ConsumerThread = new ConsumerThread();
ConsumerThread.start();
try {
ConsumerThread.sleep(2000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void putValue(){
// 生产者线程
class ProductThread extends Thread{
public void run(){
try {
conditionLearning.conditionSignal();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ProductThread ProductThread = new ProductThread();
ProductThread.start();
}
}
好了,写到这里,关于锁的讲解暂时告一段落,笔者水平有限,难免有不妥之处,请见谅。