java并发编程之美-阅读记录6
java并发包中锁
6.1LockSupport工具类
该类的主要作用就是挂起和唤醒线程,该工具类是创建锁和其他工具类的基础。LockSupport类与每个使用他的线程都关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。
1、void park()
如果调用park方法的线程已经那都了LockSupport关联的许可证的话,那LockSupport.park()会立刻返回,否则就会阻塞挂起。
package com.nxz.blog.otherTest; import java.util.concurrent.locks.LockSupport; public class TestThread004 { /**
* LockSupport park
* @param args
*/
public static void main(String[] args) {
System.out.println("main-start");
// LockSupport的park默认是不持有许可证的,也就是说,调用park方法后,当前线程会阻塞
LockSupport.park();
System.out.println("main-end");
}
}
上边运行结果:如下图,main线程会阻塞在LockSupport.park()代码处,不会输出main-end。
2、void unpark(Thread thread)方法
如果参数thread没有持有LockSupport许可,调用该方法后,会使thread持有许可证,也就是说会使调用park方法而阻塞的线程返回。(线程intercept中断之后,park方法也会返回,停止阻塞)
package com.nxz.blog.otherTest; import java.util.concurrent.locks.LockSupport; public class TestThread004 { /**
* LockSupport park
* @param args
*/
public static void main(String[] args) {
System.out.println("main-start");
// 使当前线程(main线程)获取许可
LockSupport.unpark(Thread.currentThread());
// 因为上边已经获取许可了,所以,下边这个park方法并不会阻塞线程
LockSupport.park();
System.out.println("main-end");
}
}
执行结果:
main-start
main-end
另外一个例子:
package com.nxz.blog.otherTest; import java.util.concurrent.locks.LockSupport; public class TestThread004 { /**
* LockSupport park
*
* @param args
*/
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable-start");
LockSupport.park();
System.out.println("runnable-end");
}
}); t.start();
// 目的是使t线程先执行,让t线程调用park方法后阻塞
Thread.sleep(1000);
System.out.println("main");
// 使t线程获取LockSupport许可,获取许可后,t线程就可以继续向下执行了
LockSupport.unpark(t);
System.out.println("main-end");
}
}
执行结果:
runnable-start
main
main-end
runnable-end
3、void park(long nanos)
该方法和park方法类似,只不过是在指定时间后自动返回
4、void park(Object blocker)
一般使用的是这个方法而不是无参的park方法,原因是,这个个方法输出日志时会输出阻塞的类的信息(而park方法不会输出)。
6.2抽象同步类AQS
AbstractQueuedSynchronize抽象同步队列简称AQS,是实现同步器的基础组件,并发包中锁的实现,底层都是通过AQS实现的。
1、基本属相
// 同步器是一个双向的FIFO队列 有头结点和尾节点,节点类型Node为AQS的内部类
private transient volatile Node head; private transient volatile Node tail;
// 该字段是实现锁和同步器的关键,在不同的实现类中有不同的含义,例如在ReentrantLock中代表当前线程获取可重入锁的次数,ReentrantReadWriteLock中,高16位表示读状态,也就是获取读锁的次数,低16位掉表写状态,也及时写锁的次数,Semaphore中代表限号量等等
private volatile int state;
static final class Node {
// 用来标记该线程是获取共享资源时被阻塞后挂起放入AQS队列的
static final Node SHARED = new Node();
// 用来标记该线程是获取独占资源师被阻塞后防区AQS队列的
static final Node EXCLUSIVE = null; // waitstatus状态之一, 表示线程被取消了
static final int CANCELLED = 1;
//waitstatus状态之一,表名线程需要唤醒
static final int SIGNAL = -1;
// 线程在条件队列里边等待
static final int CONDITION = -2;
// 释放共享资源师需要通知其他节点
static final int PROPAGATE = -3; // 记录当前线程的等待状态,有以上3中状态
volatile int waitStatus; // 记录当前节点的前驱节点
volatile Node prev; // 记录当前节点的后继节点
volatile Node next; // 记录当前线程
volatile Thread thread; // 下一个等待条件变量condition的节点
Node nextWaiter;
public class ConditionObject implements Condition, java.io.Serializable {
// 该类用来结合所实现线程同步的,每一个ContditionObject是一个条件变量,每一个条件变量对应一个条件对列,每一个条件队列都是一个单项链表,用来存放调用await方法后阻塞的线程
// 条件队列的第一个节点
private transient Node firstWaiter;
// 条件队列的最后一个节点
private transient Node lastWaiter;
}
6.3ReentrantLock可重入的独占锁
1、结构图:
可以看出ReentrantLock最终还是通过AQS实现的,并根据参数判断锁是公平的还是非公平的
// 默认构造是创建一个非公平锁
public ReentrantLock() {
sync = new NonfairSync();
} // 有参构造,fair:true则创建一个公平锁,false:创建非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2、void lock()
public void lock() {
sync.lock();
} // Sync类中为抽象方法,具体实现,需要看公平锁和非公平锁中的实现方法
abstract void lock(); // 非公平锁类
static final class NonfairSync extends Sync { // lock实现方法
final void lock() {
// 通过CAS操作state变量,state默认为0,表名没有被线程获取,设置为1成功后,代表该线程获取锁成功,此时state为1,并设置exclusiveThread为当前线程
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else // 调用AQS的acquire方法,AQS内部会条用tryAcquire方法
acquire(1);
} protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// 公平锁
static final class FairSync extends Sync {
final void lock() {
acquire(1);
} protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
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;
}
}
3、lockInterruptibly()方法,可中断的lock方法
和lock类似,区别就是能够对中断进行相应(而lock方法对于中断操作是忽视的)
4、trylock()方法
如果当前锁没有被其他线程持有,则调用该方法时会立即返回,如果被其他线程持有,则该方法也会立即返回false。(该方法不会阻塞,lock方法会阻塞,即会进入阻塞队列中)。
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
} final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果state为0,即该锁没有被其他线程持有,则该线程通过CAS操作后,持有锁,会理解返回true
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果持有锁的线程是当前线程,则state累计额acquires后,返回true
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 返回false,在锁被其他线程持有时会立即返回false
return false;
}
5、释放锁unlock()
如果所被当前线程持有,则state赋为0,即释放锁,如果所被当前线程多次持有,则state只是减1,并不会释放锁。如果当前线程没有持有锁,则跑异常。
6.4ReentrantReadWriteLock读写锁
采用读写分离的策略,允许多个线程可以同时获取锁。
1、结构:有两个锁,WriteLock和ReadLock
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
// 读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
// 写锁 独占锁
private final ReentrantReadWriteLock.WriteLock writerLock;
// 同步时 继承自AQS类
final Sync sync; public ReentrantReadWriteLock() {
this(false);
} public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
} public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
}
ReentrantReadWriteLock和ReentrantLock类似,只不过内部分为写锁和读锁,对于aqs中state变量的控制,在ReentrantLock中,0表示未被线程获取,而在读写锁中,将state分成两份,高16位负责记录读锁和低16位负责写锁。
6.5jdk8中新增的StampedLock锁
该锁是jdk8中新增的,提供了3中模式的读写控制,当调用获取锁的函数时,会返回一个long类型的变量,也就是戳记(stamp),代表锁的状态。当调用释放锁和转换锁的时候,需要将该stamp作为参数传入。
写锁writeLock:是一个独占锁,同一时间只能有一个线程可以获取锁(并且是不可冲入锁)
悲观读锁readLock:是一个共享锁,在没有线程获取的情况下多个线程可以获取到锁,但是只要有线程获取到写锁,则获取读锁的线程都会阻塞(同时该锁也是不可冲入锁)
乐观读锁tryOptimisticRead
使用案例:
/**
* jdk8中stampedLock中提供的例子
* 管理二维点的类
*/
class Point {
private double x, y;
private final StampedLock sl = new StampedLock(); /**
* 独占的方法
*/
void move(double deltaX, double deltaY) { // an exclusively locked method
// 获取写锁
long stamp = sl.writeLock();
// x y坐标调整
try {
x += deltaX;
y += deltaY;
} finally {
// 释放写锁
sl.unlockWrite(stamp);
}
} /**
* 共享方法,使用了乐观的共享锁
*/
double distanceFromOrigin() { // A read-only method
// 获取乐观的读锁
long stamp = sl.tryOptimisticRead();
// 获取point对象坐标的拷贝
double currentX = x, currentY = y;
// 验证stamp(也就是之前获取的锁是否仍然可用),如果可用的话,则直接进行运算,不可用的话,则获取一个悲观的读锁readlock
if (!sl.validate(stamp)) {
// 在stamp不可用情况下,重新获取一个悲观读锁
stamp = sl.readLock();
try {
// 重新设置xy的拷贝
currentX = x;
currentY = y;
} finally {
// 释放悲观读锁
sl.unlockRead(stamp);
}
}
// 返回两点之间的距离
return Math.sqrt(currentX * currentX + currentY * currentY);
} // 更原点
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
// 如果x=y=0是,修改坐标
while (x == 0.0 && y == 0.0) {
// 将之前获取到的读锁转换为一个写锁
long ws = sl.tryConvertToWriteLock(stamp);
//ws不等于0,则代表锁转换成功
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
} else {
// 转换失败后,释放读锁,重新获取一个写锁,重复while循环
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
// 释放锁
sl.unlock(stamp);
}
}
}
stampedlock和ReentrantReadWriteLock类似,只不过前者是不可重入锁,但是前者在提供的乐观读锁在多线程环境下提供了更好的性能,这是因为乐观读锁不需要进行CAS操作设置锁的状态,只是简单的验证了一下锁的stamp是否可用。
java并发编程之美-阅读记录6的更多相关文章
- java并发编程之美-阅读记录1
1.1什么是线程? 在理解线程之前先要明白什么是进程,因为线程是进程中的一个实体.(线程是不会独立存在的) 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程中的 ...
- java并发编程之美-阅读记录11
java并发编程实践 11.1ArrayBlockingQueue的使用 有关logback异步日志打印中的ArrayBlockingQueue的使用 1.异步日志打印模型概述 在高并发.高流量并且响 ...
- java并发编程之美-阅读记录2
2.1什么是多线程并发编程 并发:是指在同一时间段内,多个任务同时在执行,并且执行没有结束(同一时间段又包括多个单位时间,也就是说一个cpu执行多个任务) 并行:是指在单位时间内多个任务在同时执行(也 ...
- java并发编程之美-阅读记录10
同步器 10.1CountDownLatch 在开发过程中经常会遇到在主线程中开启多个子线程去并行执行任务,并且主线程需要等待子线程执行完毕后在进行汇总.在CountDownLatch出现之前使用线程 ...
- java并发编程之美-阅读记录7
java并发包中的并发队列 7.1ConcurrentLinkedQueue 线程安全的无界非阻塞队列(非阻塞队列使用CAS非阻塞算法实现),其底层数组使用单向列表实现,对于出队和入队操作使用CAS非 ...
- java并发编程之美-阅读记录5
java并发包中的并发List 5.1CopeOnWriteArrayList 并发包中的并发List只有CopyOnWriteArrayList,该类是一个线程安全的arraylist,对其进行的修 ...
- java并发编程之美-阅读记录4
java并发包中的原子操作类,这些类都是基于非阻塞算法CAS实现的. 4.1原子变量操作类 AtomicInteger/AtomicLong/AtomicBoolean等原子操作类 AtomicLon ...
- java并发编程之美-阅读记录3
java并发包中的ThreadLocalRandom类,jdk1.7增加的随机数生成器 Random类的缺点:是多个线程使用同一个原子性的种子变量,导致对原子变量的更新产生竞争,降低了效率(该类是线程 ...
- Java并发编程之美之并发编程线程基础
什么是线程 进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程至少有一个线程,进程的多个线程共享进程的资源. java启动main函数其实就 ...
随机推荐
- Web前端基础学习-1
HTML5/CSS简介 首先来说一说什么是HTML5,HTML5可以认为是字面上的意义,也就是HTML的第五代产品,当然从另一个角度来说它是一种新的富客户端解决方案. HTML5 将成为 HTML.X ...
- regex - POSIX 1003.2 正则表达式
DESCRIPTION 正则表达式 (``RE''s), 在 POSIX 1003.2 中定义,包含两种类型:新式 REs (基本上指的是 egrep 使用的那些,1003.2 称其为 ``exten ...
- AES-OZ745 OZ745 Zynq-7000 开发板与套件
北京太速科技有限公司为广大合作单位特设海外代购业务,主要包括各类板卡.相机.传感器.仪器仪表.专用芯片等.代购业务仅收取基本的手续费. 北京太速科技有限公司在线客服:QQ:448468544 淘宝网站 ...
- USB接口外壳地和信号地间的处理
USB外壳地和信号地之间串接1M电阻,并且还接一个0.01uf的电容到信号地,能否将一下这样处理的原理和目的: 1.将影响外壳的噪音滤除,不影响信号地: 2.迫使板子上电流是流入内部的信号地,而不是流 ...
- 环境管理 pipenv 的 使用
安装 pip3 install pipenv 配置 配置 环境变量 WORKON_HOME , 表示 生成的虚拟环境 文件 的 存放位置 创建虚拟环境 方式一 pipenv --python 3.7 ...
- windows10安装nodejs 10和express 4
最进做一个个人博客系统,前端用到了semanticUI,但是要使用npm工具包,所以需要安装nodejs,nodejs自带npm 下载 去官网下载自己系统对应的版本,我的是windows:下载 可以在 ...
- go语言从例子开始之Example39.使用函数自定义排序
有时候我们想使用和集合的自然排序不同的方法对集合进行排序.例如,我们想按照字母的长度而不是首字母顺序对字符串排序.这里是一个 Go 自定义排序的例子. Example: package main im ...
- LeetCode(力扣)——Search in Rotated Sorted Array 搜索旋转排序数组 python实现
题目描述: python实现 Search in Rotated Sorted Array 搜索旋转排序数组 中文:假设按照升序排序的数组在预先未知的某个点上进行了旋转. ( 例如,数组 [0,1 ...
- STREAM Benchmark及其操作性能分析
STREAM 是业界广为流行的综合性内存带宽实际性能 测量 工具之一.随着处理器处理核心数量的增多,内存带宽对于提升整个系统性能越发重要,如果某个系统不能够足够迅速地将内存中的数据传输到处理器当中,若 ...
- springboot使用异步查询数据
主要适用于需要查询多种类型的数据,而且二者的参数没有关联的情况. 1.开启异步调用注解 2.创建抽象类,定义相关方法 /** * @author:YZH * time: 2019/8/8 12:16 ...