lesson3:java的锁机制原理和分析
jdk1.5之前,我们对代码加锁(实际是对象加锁),都是采用Synchronized关键字来处理,jdk1.5及以后的版本中,并发编程大师Doug Lea在concurrrent包中提供了Lock机制。两种机制在性能上目前的jdk版本都差不多,Synchronized作为jvm的关键字,是在jvm层面实现的锁机制,而Lock机制是在java语言这个级别实现的锁机制,其实锁的核心原理都是为某个对象加锁。本文中将以Lock机制的源代码来分析锁机制的原理和实现,后面的demo代码也将按照Lock锁来展开。
demo源码:https://github.com/mantuliu/javaAdvance 中的类 Lesson3CoarseGrainedLock和Lesson3FinedGrainedLock
下面的斜体段落是java api中对Lock的描述:
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。
synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。
虽然 synchronized 方法和语句的范围机制使得使用监视器锁编程方便了很多,而且还帮助避免了很多涉及到锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。例如,某些遍历并发访问的数据结果的算法要求使用  "hand-over-hand" 或 "chain locking":获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁,从而支持使用这种技术。
随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句: 
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。
Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。
注意,Lock 实例只是普通的对象,其本身可以在 synchronized 语句中作为目标使用。获取 Lock 实例的监视器锁与调用该实例的任何 lock() 方法没有特别的关系。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock 实例。
除非另有说明,否则为任何参数传递 null 值都将导致抛出 NullPointerException。
内存同步
所有 Lock 实现都必须 实施与内置监视器锁提供的相同内存同步语义,如 The Java Language Specification, Third Edition (17.4 Memory Model) 中所描述的: 
- 成功的 lock操作与成功的 Lock 操作具有同样的内存同步效应。
- 成功的 unlock操作与成功的 Unlock 操作具有同样的内存同步效应。
不成功的锁定与取消锁定操作以及重入锁定/取消锁定操作都不需要任何内存同步效果。
实现注意事项
三种形式的锁获取(可中断、不可中断和定时)在其性能特征、排序保证或其他实现质量上可能会有所不同。而且,对于给定的 Lock 类,可能没有中断正在进行的 锁获取的能力。因此,并不要求实现为所有三种形式的锁获取定义相同的保证或语义,也不要求其支持中断正在进行的锁获取。实现必需清楚地对每个锁定方法所提供的语义和保证进行记录。还必须遵守此接口中定义的中断语义,以便为锁获取中断提供支持:完全支持中断,或仅在进入方法时支持中断。
由于中断通常意味着取消,而通常又很少进行中断检查,因此,相对于普通方法返回而言,实现可能更喜欢响应某个中断。即使出现在另一个操作后的中断可能会释放线程锁时也是如此。实现应记录此行为。
Lock接口的实现类有:ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock;本文将以ReentrantLock的源码来分析内部实现原理:
a.lock()方法
    public void lock() {
        sync.lock();//调用sync对象的lock()方法
    }
下面看看sync对象的创建过程及它的lock()方法
    public ReentrantLock() {
        sync = new NonfairSync();//我们发现实现sync对象的类是NonfairSync
    }
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() {
/*调用unsafe的compareAndSwapInt方法(原子操作),如果Lock对象存储的对象值是0(与第一个参数值相同),则将此值设置为1(与第二个参数值相同),反之,则返回失败*/
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());//设置当前线程为锁的持有者
else
acquire(1);//当前线程等待其它线程释放锁
}
}
b.tryLock()方法:仅在调用时锁为空闲状态才获取该锁
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);//尝试获取锁
    }
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//获取到当前线程
            int c = getState();//获取锁的状态
            if (c == 0) {//0表示锁未被占用
                if (compareAndSetState(0, acquires)) {//调用unsafe的compareAndSwapInt方法(原子操作),标识此锁为已经占有状态
                    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;
        }
c.tryLock(long time, TimeUnit unit)方法,介于lock()和tryLock()方法之间,如果锁已经被其它线程占用,则会等待一段时间
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
} public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) || //调用NonfairSync的tryAcquire(方法),与上面的tryLock()方法调用的nonfairTryAcquire()一致
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
long lastTime = System.nanoTime();//按纳秒返回当前时间
final Node node = addWaiter(Node.EXCLUSIVE);//创建一个节点并入队列来等待锁
boolean failed = true;
try {
for (;;) {//一直循环
final Node p = node.predecessor();//返回当前节点的前一个等待节点
if (p == head && tryAcquire(arg)) {//如果前一个节点已经是head节点,则试图去获取锁,如果获取到了,则执行下面的代码
setHead(node);//设置当前节点为队列中的头节点
p.next = null; // help GC
failed = false;
return true;
}
if (nanosTimeout <= 0)
return false;//超时返回失败
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
long now = System.nanoTime();
nanosTimeout -= now - lastTime;
lastTime = now;
if (Thread.interrupted())//线程被中断了
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
d.lockInterruptibly()方法,可以相应线程中断的去获取锁,如果线程被中断了,则抛出异常
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);//调用acquireInterruptibly
    }
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())//如果线程被中断了,则抛出异常
            throw new InterruptedException();
        if (!tryAcquire(arg))////调用NonfairSync的tryAcquire(方法),与上面的tryLock()方法调用的nonfairTryAcquire()一致
            doAcquireInterruptibly(arg);
    }
    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) && //检测线程是否被中断
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
e.unlock()方法,释放锁,通常只有持有锁的线程才能释放锁成功
    public void unlock() {
        sync.release(1);
    }
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            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;
        }
上面的代码分析了ReentrantLock的非公平锁的实现,在实现的使用过程中,锁和线程是息息相关的,如果我们把线程比作高速公路上的车道,那么行驶在高速公路上的车辆就是我们实际的代码(操作),因为路况或其它原因,各条车道需要并线,为防止意外发生,在一定时间内只能有一条车道或几条路上的车可以通过(代码上了锁),为保证通行时间,有两点需要注意:1是车辆通过的速度要快(线程占用锁的时间要少),2是在高速公路的车道数足够多的情况下,要尽可能的将车道分成多个小组,各个小组内部交替通行,小组与小组之间不影响。总结起来,在使用锁的情况下,要想对系统的性能影响足够小:1.锁的粒度要足够小,尽量减少同时使用同一把锁的线程数量;2.加锁的线程执行时间要足够快。下面的demo从粒度的角度展示了锁对于性能的影响:
先来看看粗粒度的锁,100个线程锁同一个资源,每个线程执行1秒钟的情况:
package com.mantu.advance; import java.util.concurrent.locks.ReentrantLock; /**
* blog http://www.cnblogs.com/mantu/
* github https://github.com/mantuliu/
* @author mantu
*
*/
public class Lesson3CoarseGrainedLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static void main(String args[]){
for(int i=0;i<100;i++){
Thread thread =new Thread(new Lesson3CoarseGrainedLock());
thread.start();
}
} @Override
public void run() {
try {
lock.lock();//100个线程使用同一个锁,每个时刻只能有一个线程执行
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally{
lock.unlock();
System.out.println("线程"+Thread.currentThread().getId()+"执行完毕");
}
}
}
再来看看细粒度锁的demo,每10个线程分为一个组,组内的线程才会存在资源竞争的情况,执行完代码后,会发现效率比上一个demo提升了非常多
package com.mantu.advance; import java.util.HashMap;
import java.util.concurrent.locks.ReentrantLock; /**
* blog http://www.cnblogs.com/mantu/
* github https://github.com/mantuliu/
* @author mantu
*
*/
public class Lesson3FinedGrainedLock implements Runnable{
public static HashMap<Integer,ReentrantLock> lockMap = new HashMap<Integer,ReentrantLock>();
public static void main(String args[]){
init();
for(int i=0;i<100;i++){
Thread thread =new Thread(new Lesson3FinedGrainedLock());
thread.start();
}
}
public static void init(){
for(int i=0;i<10;i++){
lockMap.put(new Integer(i),new ReentrantLock());
}
}
@Override
public void run() {
ReentrantLock lock = Lesson3FinedGrainedLock.lockMap.get((int)(Thread.currentThread().getId())%10);
try {
lock.lock();//细粒度的锁,效率提高非常多
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally{
lock.unlock();
System.out.println("线程"+Thread.currentThread().getId()+"执行完毕");
}
}
}
lesson3:java的锁机制原理和分析的更多相关文章
- Atitit.软件与编程语言中的锁机制原理attilax总结
		Atitit.软件与编程语言中的锁机制原理attilax总结 1. 用途 (Db,业务数据加锁,并发操作加锁.1 2. 锁得类型 排它锁 "互斥锁 共享锁 乐观锁与悲观锁1 2.1. 自旋锁 ... 
- Java的锁机制--synchronsized关键字
		引言 高并发环境下,多线程可能需要同时访问一个资源,并交替执行非原子性的操作,很容易出现最终结果与期望值相违背的情况,或者直接引发程序错误. 举个简单示例,存在一个初始静态变量count=0,两个线程 ... 
- Java CAS同步机制 原理详解(为什么并发环境下的COUNT自增操作不安全): Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性。
		精彩理解: https://www.jianshu.com/p/21be831e851e ; https://blog.csdn.net/heyutao007/article/details/19 ... 
- 深入浅出 Java Concurrency 锁机制 : AQS
		转载:http://www.blogjava.net/xylz/archive/2010/07/06/325390.html 在理解J.U.C原理以及锁机制之前,我们来介绍J.U.C框架最核心也是最复 ... 
- java的锁机制
		一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在Java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线 ... 
- Java常用锁机制简介
		在开发Java多线程应用程序中,各个线程之间由于要共享资源,必须用到锁机制.Java提供了多种多线程锁机制的实现方式,常见的有synchronized.ReentrantLock.Semaphore. ... 
- java的锁机制——synchronized
		一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线 ... 
- CAS无锁机制原理
		原子类 java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程 原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读 ... 
- Java偏向锁实现原理(Biased Locking)
		http://kenwublog.com/theory-of-java-biased-locking 阅读本文的读者,需要对Java轻量级锁有一定的了解,知道lock record, mark wor ... 
随机推荐
- C#几种截取字符串的方法小结,需要的朋友可以参考一下
			1.根据单个分隔字符用split截取 例如 复制代码 代码如下: string st="GT123_1"; string[] sArray=st.split("_&quo ... 
- 文本溢出、垂直外边距合并、BFC、hasLayout
			今天学习文本溢出,又遇到了一些小问题,先上图: 关于文本溢出推荐:http://www.cnblogs.com/yzg1/p/5089534.html 从里面学习到单行文本和多行文本溢出, overf ... 
- div中的内容居中
			要使div中的内容居中显示,不仅div要设定“text-align:centr" ,内置对象要添加margin:auto;属性才能使其在firefox等其他浏览器中也能居中. 
- Shell中的数值计算
			#!/bin/bash echo "please input number:" read n a=`expr $n / 100` #a1=`expr $n - $a * 100` ... 
- [转]C++智能指针的创建
			zero 坐在餐桌前,机械的重复“夹菜 -> 咀嚼 -> 吞咽”的动作序列,脸上用无形的大字写着:我心不在焉.在他的对面坐着 Solmyr ,慢条斯理的吃着他那份午餐,维持着他一贯很有修养 ... 
- tkinter之文件对话框
			from tkinter import * from tkinter.filedialog import * filetype = [('Python Files', '*.py *.pyw'), ( ... 
- DBA优化SQL采用的WITH AS 用法简介
			一.WITH AS简介 WITH AS的用法从oracle 9i新增的,官方文档也称之为:subquery factoring;在进行复杂的查询.统计等操作时使用with as 子句可以大大提高性能! ... 
- Bash shell 笔记总结(一) 转自http://www.bubuko.com/infodetail-509992.html,谢谢原作者
			本文是上课笔记总结,涉及细节知识点会在以后文章说明! bash脚本编程: 脚本程序:解释器解释执行: shell: 交互式接口:编程环境: shell: 能够提供一些内部命令,并且能通过PATH环境变 ... 
- 在Mvc中创建WebApi是所遇到的问题
			1.提示"The 'ObjectContent`1' type failed to serialize the response body for content type 'applica ... 
- jsp中的动作元素:<jsp:plugin>
			<jsp:plugin>用来产生客户端浏览器的特别标签(object或embed),可以使用它来插入Applet或JavaBean. 当jsp文件被编译把结果发给浏览器是,<jsp: ... 
