Java线程同步之一

Wesley13
• 阅读 715

一、线程同步

线程同步是指两个并发执行的线程在同一时间不同时执行某一部分的 程序。同步问题在生活中也很常见,就比如在麦当劳点餐,假设只有一个服务员能够提供点餐服务。每个服务员在同一时刻只能接待一个顾客的点餐,那么除了正在 接待的顾客,其他人只能等待排队。当一个点餐服务完成之后,其他顾客就可以上去进行点餐。

从这个例子中可以看到如下几个关注点:

点餐服务为临界区域(critical area),其可同时进行的数量,即为有多少人可进入临界区域。

排队即为对目前暂时无法取得点餐服务的人的一种处理方式。这种处理方式的特性有公平性(按次序),效率性(接手最快为最好)等。

顾客进行排队和从队伍中叫一个顾客来进行服务即为睡眠(park)和唤醒(unpark)机制

并发中线程同步是重点需关注的问题,线程同步自然也有一定的模式,DougLea就写出了一个简单的框架AQS用来支持一大类线程同步工具,如ReentrantLock,CountdownLatch,Semphaore等。

AQS是concurrent包中的一系列同步工具的基础实现,其提供了状态位,线程阻塞-唤醒方法,CAS操作。基本原理就是根据状态位来控制线程的入队阻塞、出队唤醒来解决同步问题。

Java线程同步之一

入队:

Java线程同步之一

出队:

Java线程同步之一

二、代码分析

下面以ReentrantLock来说明AQS的组成构件的工作情况:

在ReentrantLock中封装了一个同步器Sync,继承 了AbstractQueuedSynchronizer,根据对临界区的访问的公平性要求不同,又分为NonfairSync和FairSync。为了 简化起见,就取最简单的NonFairSync作为例子来说明:

1. 对于临界区的控制:

java.util.concurrent.locks.ReentrantLock.NonfairSync

final void lock() {
   2:  
   3:         if (compareAndSetState(0, 1))
   4:  
   5:         setExclusiveOwnerThread(Thread.currentThread());
   6:  
   7:         else
   8:  
   9:         acquire(1);
  10:  
  11:     }

从以上代码可以看出,其主要目的是采用cas比较临界区的状态。

1.1. 如果为0,将其设置为1,并记录当前线程(当前线程可进入临界区);

1.2. 如果为1,尝试获取临界区控制

java.util.concurrent.locks.AbstractQueuedSynchronizer

public final void acquire(int arg) {
   2:  
   3:             if (!tryAcquire(arg) &&
   4:  
   5:                 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
   6:  
   7:                 selfInterrupt();
   8:  
   9:           }

1.2.1. NonFairLock的tryAcquire实现为:

final boolean nonfairTryAcquire(int acquires) {
   2:  
   3:               final Thread current = Thread.currentThread();
   4:  
   5:               int c = getState();
   6:  
   7:               if (c == 0) {
   8:  
   9:               if (compareAndSetState(0, acquires)) {
  10:  
  11:                  setExclusiveOwnerThread(current);
  12:  
  13:                  return true

上述代码主要是针对大部分线程进入临界区工作时间不会很长而进行的性能优化,第一次尝试失败了,极有可能过一会儿锁就释放了,因此重新去尝试获取锁。

1.2.2. 以下这段代码是锁的精华部分

java.util.concurrent.locks.AbstractQueuedSynchronizer

for (;;) {
   8:  
   9: final Node p = node.predecessor();
  10:  
  11: if (p == head && tryAcquire(arg)) {
  12:  
  13: setHead(node);
  14:  
  15: p.next = null; // help GC
  16:  
  17: return interrupted;
  18:  
  19: }

在无限循环中完成了对线程的阻塞和唤醒。阻塞在parkAndCheckInterrupt()唤醒后从此处进行释放。

算法过程:

从加入队列的node开始反向查找,将前一个元素赋值给p;

如果p是head,那么试着再获得一次锁tryAcquire(arg),成功则将head指针往后移动,并跳出循环;

如果上一步骤尝试失败,那么进行测试是否要park ,如果状态为0,将其标记为SIGNAL,并返回false;

再重复检查一次,发现其头部的waitStatus为-1.Node.signal。确认需要park successor; 进行parkAndCheckInterrupt()将当前线程阻塞。

2. 对于临界区的释放

2.1. java.util.concurrent.locks.AbstractQueuedSynchronizer

public final boolean release(int arg) {
   2:  
   3: if (tryRelease(arg)) {
   4:  
   5: Node h = head;
   6:  
   7: if (h != null && h.waitStatus != 0)
   8:  
   9: unparkSuccessor(h);
  10:  
  11: return true;
  12:  
  13: }
 return false;
  16:  
  17: }

2.1.1. java.util.concurrent.locks.ReentrantLock.Sync

protected final boolean tryRelease(int releases) {
   2:  
   3: int c = getState() - releases;
   4:  
   5: if (Thread.currentThread() != getExclusiveOwnerThread())
   6:  
   7: throw new IllegalMonitorStateException();
   8:  
   9: boolean free = false;
  10:  
  11: if (c == 0) {
  12:  
  13: free = true;
  14:  
  15: setExclusiveOwnerThread(null);
  16:  
  17: }
  18:  
  19: setState(c);
  20:  
  21: return free;
  22:  
  23: }
  24:

将state进行变化-releases,检查当前线程是否是拿住锁的线程,否则掷出异常.如果为0,将持有锁线程标记为null。

从ReentrantLock例子可以看出AQS的工作原理,更为精妙的是,在这几个基本机制作用下衍生了许多种并发工具,以后的介绍中可以看到。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java 复制Map对象(深拷贝与浅拷贝)
java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java CyclicBarrier介绍
CyclicBarrier(周期障碍)类可以帮助同步,它允许一组线程等待整个线程组到达公共屏障点。CyclicBarrier是使用整型变量构造的,其确定组中的线程数。当一个线程到达屏障时(通过调用CyclicBarrier.await()),它会被阻塞,直到所有线程都到达屏障,然后在该点允许所有线程继续执行。与CountDownLatch不同的
Wesley13 Wesley13
3年前
IDEA00 IDEA知识点汇总
一、从头搭建IDEA开发环境https://mp.weixin.qq.com/s/6jXHzkU8JfubhDsQJbwl8Q(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fmp.weixin.qq.com%2Fs%2F6jXHzkU8JfubhDsQJbwl8Q)1下
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
Java分布式锁看这篇就够了
\什么是锁?在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这