Java的数据会在CPU、Register、Cache、Heap和Thread stack之间进行复制操作,而前面四个都是在Java Threads之间共享,因此Java的锁机制主要用于解决Racing Threads的数据一致性。

基于CPU缓存一致性MESI协议的volatile关键字

保证变量在racing thread之间实时可见,使用内存屏障禁止JVM基于instruction reorder的优化,不能保证变量在各个线程之间的数据一致性(如counter++);

基于字节码机制的synchronized关键字
synchronized是语言自带的内置独享锁、非公平锁,也就是不管racing thread排队的时间先后都统一进行锁竞争,通过编排JVM字节码实现,锁为对象或者类的头标记位;悲观锁,并发性差;另外synchronized声明不会被继承,也就是如果子类方法重写父类的synchronized方法时,不再具有同步性。

使用synchronized关键字修饰代码块或者方法,表示这块代码为互斥区或临界区。有两种类型的锁可以通过synchronized加到代码块或者方法上,一种是实例Object锁,一种是class锁。对于同一个ClassLoader下加载的类而言,一个类只有一把class锁,所有这个类的实例都共享一把类锁;同一个类可以实现多个实例对象,也就存在多个实例锁。另外通过synchronized添加的锁具有可重入性,也就是只要一个线程已经获取了锁,这样只要共享同一把锁的其他synchronized修饰的代码块或者方法都可以进入,换句话说其他线程访问对其他synchronized修饰的代码块或者方法也需要等待锁的释放,因此synchronized还支持任意对象的锁,这样同一个类的不同方法可以添加不同的对象锁。

下面是基于不同锁实现的synchronized块:对象锁,静态对象锁,类锁

 public class App1 {
private Object lock1 = new Object();
private static Object lock2 = new Object();
synchronized public void funcA() {
//this object lock
}
public void funcB() {
synchronized(this) {
//this object lock
}
//run something without lock
}
public void funcC(List<String> list) {
synchronized(list) {
//list object lock
}
}
public void funcD() {
synchronized(lock1) {
//lock1 object lock
}
}
public void funcE() {
synchronized(lock2) {
//lock2 static object lock
}
}
public void funcF() {
synchronized(App1.class) {
//App1 class lock
}
}
synchronized public static void funcG() {
//App1 class lock
}
}

基于Abstract Queued Synchronizer(AQS)机制的ReentraintLock
AQS是JDK提供的的一种实现,可以实现公平和非公平锁,内部实现依赖volatile int state变量加CLH队列实现,主要的实现类是ReentrantLock;AQS定义了多线程访问共享资源的同步器框架,常见的如ReentraintLock/Semaphore/CountDownLatch等都依赖于AQS的实现;

 private volatile int state;
static final class Node {
int waitStatus;
Node prev;
Node next;
Node nextWaiter;
Thread thread;
}
protected final int getState() { return state; }
protected final void setState(int newState) { state = newState; } protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
protected boolean tryAcquire(int arg) {}
protected boolean tryRelease(int arg) {}
protected int tryAcquireShared(int arg) {}
protected boolean tryReleaseShared(int arg) {}
protected boolean isHeldExclusively() {}

AQS通过维护一个FIFO队列,并且通过一个由volatile修饰的int状态值来实现锁的获取,int状态值通过CPU的compare-and-swap指令进行更新,从而保证原子性。FIFO队列中每一个Node表示一个排队线程,其保存着线程的引用和状态,然后通过三个方法分别对获取或者设置状态。通过对getState,setState和compareAndSetState的封装,AQS的继承类需要试下如下几个方法,前面两个表示获取和释放独占锁(如ReentraintLock),后面两个表示获取和释放共享锁(如Semaphore和CountDownLatch)。

ReentrantLock初始化状态state=0,线程A访问同步代码的时候使用ReentrantLock.lock(),内部会调用tryAcquire尝试获取独占锁,状态变成state+1;其他线程调用ReentrantLock.lock()的时候就会失败,直到线程A调用unlock(内部为tryRelease)将状态编程state=0;如果线程A在持有独占锁的同时访问其他同步代码块,这时候state的值就会累加,需要调用unlock(内部为tryRelease)减少state的值。ReentrantLock也提供了类似wait/notify的方法,await/signal,同样的线程在调用这两个方法之前需要获得对象锁监视,也就是执行lock.lock()方法。

ReentrantLock是纯粹的独占锁,为了提升效率引入了ReentrantReadWriteLock –> readLock/writeLock,读读共享,读写互斥,写写互斥。CLH队列中的节点模式分为shared和exclusive两种,当一个线程修改了state状态则表示成功获取了锁,如果线程的模式是shared则会执行一个传递读锁的过程,策略是从CLH队列的头到尾依次传递读锁,直到遇到一个模式为exclusive的写锁模式的节点,这个exclusive模式的节点需要等之前所有shared模式的节点对应的操作都执行完毕之后才会获取到锁,这就是读写锁的模式。

 public class App1 extends Thread {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public App1() {
super();
}
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()
+ " : start to wait.");
condition.await();//condition.signal();
System.out.println(Thread.currentThread().getName()
+ " : wait ends, execute again.");
} catch (Exception e) {
} finally {
lock.unlock();
}
}
}

基于Compare-And-Swap机制的AtomicBoolean/AtomicReference/AtomicInteger,可实现自旋锁(spin-lock),乐观锁
基于volatile关键字的变量虽然可以保证各个线程的实时可见,但一旦发生更新操作,则可能发生线程间数据不一致的发生,常见的一种场景是多线程共享的计数器,volatile不能友好解决counter++这样的更新操作,这个时候可以使用AtomicInteger来保证;AtomicInteger内部结合volatile修饰int变量,native类型的unsafe.compareAndSwapInt操作,和基于内存偏移量的compare-and-swap操作实现乐观锁的原子操作,最终可以保证Atomic类数据更新的线程安全;

下面代码是AtomicInteger类所有封装操作的内部实现,目标变量的内存值可以通过aInteger和objectValueOffset唯一确定,首先取出期望值current,然后通过自旋操作对比当前线程的期望值current与当前内存中的值是否匹配,如果匹配则更新为current + increment,否则继续自旋;排队自旋锁(ticket spin-lock)正是基于这一机制实现的;

 public final int getAndAddInt(Object aInteger, long objectValueOffset,
int increment) {
int current;
do {
current = this.getIntVolatile(aInteger, objectValueOffset);
} while (!this.compareAndSwapInt(aInteger, objectValueOffset,
current, current + increment));
return current;
}

相比synchronized关键字的悲观锁实现,CAS使用乐观锁机制在并发性方面具有比较大的优势,但受限于CAS的实现机制,AtomicInteger也存在一些问题,ABA问题,CPU开销大,不能保证两个或者以上变量的原子性操作;

对Java提供的锁机制的一些思考的更多相关文章

  1. 深入浅出Java并发包—锁机制(三)

    接上文<深入浅出Java并发包—锁机制(二)>  由锁衍生的下一个对象是条件变量,这个对象的存在很大程度上是为了解决Object.wait/notify/notifyAll难以使用的问题. ...

  2. JAVA多线程与锁机制

    JAVA多线程与锁机制 1 关于Synchronized和lock synchronized是Java的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码 ...

  3. 深入浅出Java并发包—锁机制(二)

    接上文<深入浅出Java并发包—锁机制(一)  >  2.Sync.FairSync.TryAcquire(公平锁) 我们直接来看代码 protected final boolean tr ...

  4. 深入浅出Java并发包—锁机制(一)

    前面我们看到了Lock和synchronized都能正常的保证数据的一致性(上文例子中执行的结果都是20000000),也看到了Lock的优势,那究竟他们是什么原理来保障的呢?今天我们就来探讨下Jav ...

  5. JAVA中关于锁机制

    本文转自 http://blog.csdn.net/yangzhijun_cau/article/details/6432216 一段synchronized的代码被一个线程执行之前,他要先拿到执行这 ...

  6. Java 中的锁机制

    多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发(线程安全)问题.解决并发问题可以用锁. java的内置锁: 每个java对象都可以用做一个实现同步的锁,这些锁称为内置锁.线程进入同步 ...

  7. 深入浅出 Java Concurrency (6): 锁机制 part 1 Lock与ReentrantLock

      前面的章节主要谈谈原子操作,至于与原子操作一些相关的问题或者说陷阱就放到最后的总结篇来整体说明.从这一章开始花少量的篇幅谈谈锁机制. 上一个章节中谈到了锁机制,并且针对于原子操作谈了一些相关的概念 ...

  8. 【转载】Java中的锁机制 synchronized & 偏向锁 & 轻量级锁 & 重量级锁 & 各自优缺点及场景 & AtomicReference

    参考文章: http://blog.csdn.net/chen77716/article/details/6618779 目前在Java中存在两种锁机制:synchronized和Lock,Lock接 ...

  9. 深入浅出 Java Concurrency (9): 锁机制 part 4 锁释放与条件变量 (Lock.unlock And Condition)

    本小节介绍锁释放Lock.unlock(). Release/TryRelease unlock操作实际上就调用了AQS的release操作,释放持有的锁. public final boolean ...

随机推荐

  1. Html5shiv ---- 让IE低版本浏览器识别并支持HTML5标签

    Html5shiv.js是针对IE浏览器的 javaScript 补丁,作用如题 该脚本的下载链接 使用使在head标签中使用script标签引用即可

  2. 学习Mahout(三)

    开发+运行第一个Mahout的程序 代码: /** * Licensed to the Apache Software Foundation (ASF) under one or more * con ...

  3. PostgreSQL - pgAdmin4远程连接数据库

    前言 PostgreSQL在安装的时候自带的pgAdmin这个可视化工具,自从将PostgreSQL9升级到了10版本后,自带的pgAdmin也从3升级到了4版本.pgAdmin4的变化非常巨大,刚接 ...

  4. Git,SVN的优缺点及适合的范围,开源项目?公司项目?

    Git,SVN的优缺点及适合的范围,开源项目?公司项目? 使用git不久,粗浅理解: 1)适用对象不同.Git适用于参与开源项目的开发者.他们由于水平高,更在乎的是效率而不是易用性.Svn则不同,它适 ...

  5. [Python]IndentationError: unindent does not match any outer indentation level

    这个是缩进没对齐 可能是混用了tab与空格,到这里显示空白就可以看出来.

  6. STP-2-三个选择

    1.选择根交换机 2.确定根端口 3.确定指定端口     1.选择根交换机   stp中只有一台交换机能成为根(Root),每台交换机按自己的STP逻辑,先发一个hello称自己为根,如果收到了比自 ...

  7. ZROI 部分题目题解

    ZROI 部分题目题解 335 首先发现一个性质: 对于最短的边而言,所有点的路径如果经过了这条边,那么路径的权值就是这条边的边权(废话) 那么我们把最短的边拎出来,可以发现,博物馆确定时,每个点按照 ...

  8. Codeforces 1139E(二分图最大匹配)

    pi只有0-5000且只找最小的没出现的,又要找不同club的,考虑二分匹配,左边pi,右边ci,一个匹配一个.离线倒着加边即可. const int maxn = 5e3 + 5; int m, n ...

  9. Springboot的static和templates

    static和templates部分参考博客:https://blog.csdn.net/wangb_java/article/details/71775637 热部署参考博客:https://www ...

  10. centos7安装文档

    1.当载入安装镜像时,我们会看到如下图中的画面,我们选择第一项,安装centos7 2.选择英语(个人测试环境可以使用中文安装) 3.选择network&hostname配置网络 4.在配置网 ...