From 2f780b2ede009b8ce10e95acd10797a1579d2cb5 Mon Sep 17 00:00:00 2001 From: fyang21117 <1135783636@qq.com> Date: Fri, 27 Dec 2019 16:03:45 +0800 Subject: [PATCH 1/7] add week_03/22/AbstractQueuedSynchronizer-22.md. --- week_03/22/AbstractQueuedSynchronizer-22.md | 134 ++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 week_03/22/AbstractQueuedSynchronizer-22.md diff --git a/week_03/22/AbstractQueuedSynchronizer-22.md b/week_03/22/AbstractQueuedSynchronizer-22.md new file mode 100644 index 0000000..4838889 --- /dev/null +++ b/week_03/22/AbstractQueuedSynchronizer-22.md @@ -0,0 +1,134 @@ +import java.util.concurrent.locks.AbstractOwnableSynchronizer; + +/*** + * 【参考资料】 + * 深入剖析基于并发AQS的(独占锁)重入锁(ReetrantLock)及其Condition实现原理: + * https://blog.csdn.net/javazejian/article/details/75043422 + * + * + * AbstractQueuedSynchronizer又称为队列同步器(后面简称AQS),它是用来构建锁或其他同步组件的基础框架, + * 内部通过一个int类型的成员变量state来控制同步状态, + * 当state=0时,则说明没有任何线程占有共享资源的锁, + * 当state=1时,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待。 + * + * AQS内部通过内部类Node构成FIFO的同步队列来完成线程获取锁的排队工作,同时利用内部类ConditionObject构建等待队列, + * 当Condition调用wait()方法后,线程将会加入等待队列中, + * 而当Condition调用signal()方法后,线程将从等待队列转移动同步队列中进行锁竞争。 + * 注意这里涉及到两种队列,一种的同步队列,当线程请求锁而等待的后将加入同步队列等待, + * 而另一种则是等待队列(可有多个),通过Condition调用await()方法释放锁后,将加入等待队列。 + * + */ + + /** + * 【参考资料】 + * AQS抽象类相关链接 + * https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/AbstractQueuedSynchronizer.html + * https://baike.baidu.com/item/AbstractQueuedSynchronizer/10627013?fr=aladdin + */ + + +// 【嵌套类摘要】 +class AbstractQueuedSynchronizer.ConditionObject +// AbstractQueuedSynchronizer 的 Condition 实现是 Lock 实现的基础。 + + +// 【构造方法摘要】 +protected AbstractQueuedSynchronizer() +// 使用初始同步状态 0 来创建一个新的 AbstractQueuedSynchronizer 实例。 + + +// 【方法摘要】 +void acquire(int arg) +// 以独占模式获取对象,忽略中断。 + +void acquireInterruptibly(int arg) +// 以独占模式获取对象,如果被中断则中止。 + +void acquireShared(int arg) +// 以共享模式获取对象,忽略中断。 + +void acquireSharedInterruptibly(int arg) +// 以共享模式获取对象,如果被中断则中止。 + +protected boolean compareAndSetState(int expect,int update) +// 如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值。 + +Collection getExclusiveQueuedThreads() +// 返回包含可能正以独占模式等待获取的线程 collection。 + +Thread getFirstQueuedThread() +// 返回队列中第一个(等待时间最长的)线程,如果目前没有将任何线程加入队列,则返回 null。 + +Collection getQueuedThreads() +// 返回包含可能正在等待获取的线程 collection。 + +int getQueueLength() +// 返回等待获取的线程数估计值。 + +Collection getSharedQueuedThreads() +// 返回包含可能正以共享模式等待获取的线程 collection。 + +protected int getState() +// 返回同步状态的当前值。 + +Collection getWaitingThreads(AbstractQueuedSynchronizer.ConditionObject condition) +// 返回一个 collection,其中包含可能正在等待与此同步器有关的给定条件的那些线程。 + +int getWaitQueueLength(AbstractQueuedSynchronizer.ConditionObject condition) +// 返回正在等待与此同步器有关的给定条件的线程数估计值。 + +boolean hasContended() +// 查询是否其他线程也曾争着获取此同步器;也就是说,是否某个 acquire 方法已经阻塞。 + +boolean hasQueuedThreads() +// 查询是否有正在等待获取的任何线程。 + +boolean hasWaiters(AbstractQueuedSynchronizer.ConditionObject condition) +// 查询是否有线程正在等待给定的、与此同步器相关的条件。 + +protected boolean isHeldExclusively() +// 如果对于当前(正调用的)线程,同步是以独占方式进行的,则返回 true。 + +boolean isQueued(Thread thread) +// 如果给定线程目前已加入队列,则返回 true。 + +boolean owns(AbstractQueuedSynchronizer.ConditionObject condition) +// 查询给定的 ConditionObject 是否使用了此同步器作为其锁定。 + +boolean release(int arg) +// 以独占模式释放对象。 + +boolean releaseShared(int arg) +// 以共享模式释放对象。 + +protected void setState(int newState) +// 设置同步状态的值。 + +String toString() +// 返回标识此同步器及其状态的字符串。 + +protected boolean tryAcquire(int arg) +// 试图在独占模式下获取对象状态。 + +boolean tryAcquireNanos(int arg,long nanosTimeout) +// 试图以独占模式获取对象,如果被中断则中止,如果到了给定超时时间,则会失败。 + +protected int tryAcquireShared(int arg) +// 试图在共享模式下获取对象状态。 + +boolean tryAcquireSharedNanos(int arg,long nanosTimeout) +// 试图以共享模式获取对象,如果被中断则中止,如果到了给定超时时间,则会失败。 + +protected boolean tryRelease(int arg) +// 试图设置状态来反映独占模式下的一个释放。 + +protected boolean tryReleaseShared(int arg) +// 试图设置状态来反映共享模式下的一个释放。 + +// 【从类 java.lang.Object 继承的方法】 +clone,equals,finalize,getClass,hashCode,notify,notifyAll,wait,wait,wait + +// 【构造方法详细信息】 +AbstractQueuedSynchronizer +protected AbstractQueuedSynchronizer() +// 使用初始同步状态 0 来创建一个新的 AbstractQueuedSynchronizer 实例。 -- Gitee From a729ed1bc115a36b3d58d6a6b7495a85a2f90b49 Mon Sep 17 00:00:00 2001 From: fyang21117 <1135783636@qq.com> Date: Fri, 27 Dec 2019 16:04:45 +0800 Subject: [PATCH 2/7] add week_03/22/ReentrantLock-22.md. --- week_03/22/ReentrantLock-22.md | 314 +++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 week_03/22/ReentrantLock-22.md diff --git a/week_03/22/ReentrantLock-22.md b/week_03/22/ReentrantLock-22.md new file mode 100644 index 0000000..0ccb8fd --- /dev/null +++ b/week_03/22/ReentrantLock-22.md @@ -0,0 +1,314 @@ +package java.util.concurrent.locks; + +import java.util.Collection; +import java.util.concurrent.TimeUnit; +import jdk.internal.vm.annotation.ReservedStackAccess; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject; + +/** + * 可重入互斥锁,其基本行为和语义与使用同步方法和语句访问的隐式监视锁相同,但具有扩展功能。 + */ +public class ReentrantLock implements Lock, java.io.Serializable { + private static final long serialVersionUID = 7373984872572414699L; + /** 提供所有实现机制的同步器 */ + private final Sync sync; + + /** + * 此锁的同步控制基础。子类到下面的公平和非空中版本。使用AQS状态表示锁上的保留数。 + */ + abstract static class Sync extends AbstractQueuedSynchronizer { + private static final long serialVersionUID = -5179523762034025860L; + + /** + * 不公平的trylock。tryAcquire在子类中实现,但两者都需要trylock方法的非空try。 + */ + @ReservedStackAccess + 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; + } + + @ReservedStackAccess + 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; + } + + protected final boolean isHeldExclusively() { +// 虽然我们必须在所有者之前处于一般读取状态,但不需要这样做来检查当前线程是否为所有者 + return getExclusiveOwnerThread() == Thread.currentThread(); + } + + final ConditionObject newCondition() { + return new ConditionObject(); + } + + // 从外部类中继的方法 + + final Thread getOwner() { + return getState() == 0 ? null : getExclusiveOwnerThread(); + } + + final int getHoldCount() { + return isHeldExclusively() ? getState() : 0; + } + + final boolean isLocked() { + return getState() != 0; + } + + /** + * 从输入流重建实例(即反序列化实例)。 + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + setState(0); // 重置到解锁状态 + } + } + + /** + * 非公平锁的同步对象 + */ + static final class NonfairSync extends Sync { + private static final long serialVersionUID = 7316153563782823691L; + protected final boolean tryAcquire(int acquires) { + return nonfairTryAcquire(acquires); + } + } + + /** + * 公平锁的同步对象 + */ + static final class FairSync extends Sync { + private static final long serialVersionUID = -3000897897090466540L; + /** + * 公平的tryAcquire版本。除非递归调用或没有服务生或是第一个,否则不要授予访问权限。 + */ + @ReservedStackAccess + 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; + } + } + + /** + * 创建ReentrantLock的实例。 + */ + public ReentrantLock() { + sync = new NonfairSync(); + } + + /** + * 使用给定的公平性策略创建ReentrantLock的实例。 + */ + public ReentrantLock(boolean fair) { + sync = fair ? new FairSync() : new NonfairSync(); + } + + /** + * 获取锁。 + */ + public void lock() { + sync.acquire(1); + } + + /** + * 获取锁,除非当前线程被中断。 + */ + public void lockInterruptibly() throws InterruptedException { + sync.acquireInterruptibly(1); + } + + /** + * 只有在调用时另一个线程未持有锁时,才获取锁。 + */ + public boolean tryLock() { + return sync.nonfairTryAcquire(1); + } + + /** + * 如果在给定的等待时间内没有被另一个线程持有并且当前线程没有被中断,则获取锁 + */ + public boolean tryLock(long timeout, TimeUnit unit) + throws InterruptedException { + return sync.tryAcquireNanos(1, unit.toNanos(timeout)); + } + + /** + * 试图释放此锁。 + */ + public void unlock() { + sync.release(1); + } + + /** + * 返回与此Lock实例一起使用的实例。 + */ + public Condition newCondition() { + return sync.newCondition(); + } + + /** + * 查询当前线程对此锁的保留数。 + */ + public int getHoldCount() { + return sync.getHoldCount(); + } + + /** + * 查询此锁是否由当前线程持有。 + */ + public boolean isHeldByCurrentThread() { + return sync.isHeldExclusively(); + } + + /** + * 查询此锁是否由任何线程持有。此方法设计用于监视系统状态,而不是用于同步控制。 + */ + public boolean isLocked() { + return sync.isLocked(); + } + + /** + * 如果此锁的公平性设置为true,则返回 true。 + */ + public final boolean isFair() { + return sync instanceof FairSync; + } + + /** + * 返回当前拥有此锁的线程(如果不拥有)。 + * 当该方法由不是所有者的线程调用时,返回值反映了当前锁定状态的尽力而为的近似值。 + * 例如,即使有线程试图获取锁,但尚未获取锁,所有者也可能是暂时的。 + * 此方法旨在促进提供更广泛锁的子类的构造监测设施。 + */ + protected Thread getOwner() { + return sync.getOwner(); + } + + /** + * 查询是否有线程正在等待获取此锁。 + * 请注意,由于取消操作可能随时发生,因此返回不保证任何其他线程将获得此锁。 + * 此方法主要用于监视系统状态。 + */ + public final boolean hasQueuedThreads() { + return sync.hasQueuedThreads(); + } + + /** + * 查询给定线程是否正在等待获取此锁。 + * 注意,由于取消可能随时发生,因此返回不保证此线程将获得此锁。 + * 此方法主要用于监视系统状态。 + */ + public final boolean hasQueuedThread(Thread thread) { + return sync.isQueued(thread); + } + + /** + * 返回等待获取此锁的线程数的估计值。 + * 该值只是一个估计值,因为当此方法遍历内部数据结构时,线程数可能会动态更改。 + * 该方法设计用于监控系统状态,不用于同步控制。 + */ + public final int getQueueLength() { + return sync.getQueueLength(); + } + + /** + * 返回包含可能正在等待获取此锁的线程的集合。 + * 由于实际的线程集在构造此结果时可能会动态更改,因此返回的集合只是一个最佳努力估计值。 + * 返回集合的元素没有特定的顺序。此方法旨在促进子类的构造,从而提供更广泛的监视设施。 + */ + protected Collection getQueuedThreads() { + return sync.getQueuedThreads(); + } + + /** + * 查询是否有任何线程正在等待与此锁关联的给定条件。 + * 注意,由于超时和中断可能随时发生,返回不能保证将来的信号将唤醒任何线程。 + * 此方法主要用于监视系统状态。 + */ + public boolean hasWaiters(Condition condition) { + if (condition == null) + throw new NullPointerException(); + if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) + throw new IllegalArgumentException("not owner"); + return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition); + } + + /** + * 返回与此锁关联的给定条件上等待的线程数的估计值。 + * 请注意,由于超时和中断可能随时发生,估计值仅用作实际服务生人数的上限。 + * 此方法设计用于监视系统状态,而不是用于同步控制。 + */ + public int getWaitQueueLength(Condition condition) { + if (condition == null) + throw new NullPointerException(); + if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) + throw new IllegalArgumentException("not owner"); + return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition); + } + + /** + * 返回一个集合,其中包含可能正在等待与此锁关联的给定条件的线程。 + * 由于实际的线程集在构造此结果时可能会动态更改,因此返回的集合只是一个最佳努力估计值。 + * 返回集合的元素没有特定的顺序。此方法旨在促进子类的构建,从而提供更广泛的状态监视设施。 + */ + protected Collection getWaitingThreads(Condition condition) { + if (condition == null) + throw new NullPointerException(); + if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) + throw new IllegalArgumentException("not owner"); + return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition); + } + + /** + * 返回标识此锁及其锁状态的字符串。 + */ + public String toString() { + Thread o = sync.getOwner(); + return super.toString() + ((o == null) ? + "[Unlocked]" : + "[Locked by thread " + o.getName() + "]"); + } +} -- Gitee From 0ba6af1ea403a1de27c66fb0826afba99d2ccc2c Mon Sep 17 00:00:00 2001 From: fyang21117 <1135783636@qq.com> Date: Fri, 27 Dec 2019 16:05:50 +0800 Subject: [PATCH 3/7] =?UTF-8?q?add=20week=5F03/22/java=E5=88=86=E5=B8=83?= =?UTF-8?q?=E5=BC=8F=E9=94=81.md.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...06\345\270\203\345\274\217\351\224\201.md" | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 "week_03/22/java\345\210\206\345\270\203\345\274\217\351\224\201.md" diff --git "a/week_03/22/java\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/week_03/22/java\345\210\206\345\270\203\345\274\217\351\224\201.md" new file mode 100644 index 0000000..0d28a73 --- /dev/null +++ "b/week_03/22/java\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -0,0 +1,117 @@ +/* + * 【参考链接】 + * 2019年12月25日09:35:01 + * 死磕 java同步系列之mysql分布式锁:https://mp.weixin.qq.com/s/Au-_hN-FcL30bIYQbLfZEQ + * 一文了解分布式锁:https://www.jianshu.com/p/31d3de863ff7 + * + * + * 【定义】 + * 分布式锁,是指在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问。 + * + * + * 【实现方案】 + * 1、基于数据库实现:唯一索引天然具有排他性,同一时刻只能允许一个竞争者获取锁。 + * (1)乐观锁机制其实就是在数据库表中引入一个版本号(version)字段来实现的。 + * (2)在Mysql中是基于 for update 来实现加锁的。 + * + * 2、基于Redis实现的锁机制:依赖redis自身的原子操作。 + * + * 3、基于Zookeeper实现:使用它的临时有序节点来实现的分布式锁。 + * 原理就是:当某客户端要进行逻辑的加锁时,就在zookeeper上的某个指定节点的目录下,去生成一个唯一的临时有序节点, + * 然后判断自己是否是这些有序节点中序号最小的一个,如果是,则算是获取了锁。如果不是,则说明没有获取到锁, + * 那么就需要在序列中找到比自己小的那个节点,并对其调用exist()方法,对其注册事件监听, + * 当监听到这个节点被删除了,那就再去判断一次自己当初创建的节点是否变成了序列中最小的。 + * 如果是,则获取锁,如果不是,则重复上述步骤。 + * + * + * 【满足要求】 + * 排他性:在同一时间只会有一个客户端能获取到锁,其它客户端无法同时获取 + * 避免死锁:这把锁在一段有限的时间之后,一定会被释放(正常释放或异常释放) + * 高可用:获取或释放锁的机制必须高可用且性能佳 + */ + + + +/*** + * 【分布式】 + * 基于Redis实现分布式锁:https://zhuanlan.zhihu.com/p/62274137 + * 【特性】 + * 安全特性:互斥访问,即永远只有一个 client 能拿到锁 + * 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区 + * 容错性:只要大部分 Redis 节点存活就可以正常提供服务 + * + * Redlock接口实现类,我们可以看到redission封装的redlock算法实现的分布式锁用法, + * 非常简单,跟重入锁(ReentrantLock)有点类似: + */ + + /** + * Redlock 实现类 + */ +@Component +public class RedissonDistributedLocker implements DistributedLocker { + + @Autowired + private RedissonClient redissonClient; + + /** + * 拿不到lock就不罢休,不然线程就一直block + * @param lockKey + * @return + */ + @Override + public RLock lock(String lockKey) { + RLock lock = redissonClient.getLock(lockKey); + lock.lock(); + return lock; + } + /** + * @param lockKey + * @param timeout 加锁时间 单位为秒 + * @return + */ + @Override + public RLock lock(String lockKey, long timeout) { + RLock lock = redissonClient.getLock(lockKey); + lock.lock(timeout, TimeUnit.SECONDS); + return lock; + } + /** + * @param lockKey + * @param unit 时间单位 + * @param timeout 加锁时间 + * @return + */ + @Override + public RLock lock(String lockKey, TimeUnit unit, long timeout) { + RLock lock = redissonClient.getLock(lockKey); + lock.lock(timeout, unit); + return lock; + } + /** + * tryLock(),马上返回,拿到lock就返回true,不然返回false。 + * 带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false. + * @param lockKey + * @param unit + * @param waitTime + * @param leaseTime + * @return + */ + @Override + public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) { + RLock lock = redissonClient.getLock(lockKey); + try { + return lock.tryLock(waitTime, leaseTime, unit); + } catch (InterruptedException e) { + return false; + } + } + @Override + public void unlock(String lockKey) { + RLock lock = redissonClient.getLock(lockKey); + lock.unlock(); + } + @Override + public void unlock(RLock lock) { + lock.unlock(); + } +} \ No newline at end of file -- Gitee From f8951cdcea94374bbb86e6002322ae86a842328a Mon Sep 17 00:00:00 2001 From: fyang21117 <1135783636@qq.com> Date: Fri, 27 Dec 2019 16:06:48 +0800 Subject: [PATCH 4/7] =?UTF-8?q?add=20week=5F03/22/java=E5=86=85=E5=AD=98?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B.md.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...05\345\255\230\346\250\241\345\236\213.md" | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 "week_03/22/java\345\206\205\345\255\230\346\250\241\345\236\213.md" diff --git "a/week_03/22/java\345\206\205\345\255\230\346\250\241\345\236\213.md" "b/week_03/22/java\345\206\205\345\255\230\346\250\241\345\236\213.md" new file mode 100644 index 0000000..5f8acfb --- /dev/null +++ "b/week_03/22/java\345\206\205\345\255\230\346\250\241\345\236\213.md" @@ -0,0 +1,57 @@ + +/** + * 【参考资料】 + * 来源:《深入理解Java虚拟机》第十二章 + * 2019年12月23日20:53:37 + * + * 内存模型(Memory Model) 【含义】: + * 在特定的操作协议(eg:MSI,MESI,MOSI,Synapse,Firefly,Dragon Protocol)下,对特定的内存或高速缓存进行读写访问的过程抽象。 + * 不同架构的物理机器拥有不同的内存模型,Java虚拟机也有自己的内存模型。 + * + * + * + * Java内存模型(JMM) + * 【定义】:屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。 + * + * 【主要目标】:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。 + * 此处的变量包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数(线程私有,不被共享,不存在竞争问题)。 + * + * 【规定】: + * (1)所有的变量存储在主内存,每条线程有自己的工作内存,其中保存了该线程使用到的变量的主内存副本拷贝, + * (2)线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。 + * (3)不同线程之间也不能直接访问对方工作内存中的变量,线程之间的变量值传递均需要通过主内存来完成。交互关系如下所示: + * + * 【Java线程①】---->【工作内存①】---->【Save 和 Load操作】---->【主内存】 + * 【Java线程②】---->【工作内存②】---->【Save 和 Load操作】---->【主内存】 + * + * 其中,工作内存对应于虚拟机栈中的部分区域,主内存对应Java堆中的对象实例数据部分。 + * + * + * 【内存间交互操作】 + * Java内存模型定义了8种操作来实现一个变量如何从主内存拷贝到工作内存(read&load)、如何从工作内存同步回主内存(store&write)等细节。 + * 虚拟机实现时保证每一种操作都是原子的(Atomic)、不可再分的(Indivisible)。 + * (1)lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。 + * (2)unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。 + * (3)read(读取):作用于主内存的变量,它把一个变量的值从主内存中传输到线程的工作内存中,以便随后的load操作。 + * (4)load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。 + * (5)use(使用):作用于工作内存的变量,它把工作内存中的一个变量的值传递给执行引擎, + * 每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。 + * (6)assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量, + * 每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 + * (7)store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。 + * (8)write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。 + *. + * 注意:read&load和store&write都必须按顺序执行,不一定连续执行。 + * 规则限定(确定Java程序中哪些内存访问操作在并发下是安全的): + * (1)不允许read&load、store&write操作之一单独出现。即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写但主存不接受的情况。 + * (2)不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了必须把该变化同步回主内存。 + * (3)不允许一个线程无原因地把数据从线程的工作内存同步回主内存(没有任何assign操作)。 + * (4)一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load&assign)的变量。 + * (5)一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次;只有执行相同次数的unlock操作,变量才能被解锁。 + * (6)如果对一个变量执行lock操作,那将会清空工作内存中此变量的值。在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。 + * (7)如果一个变量没有先被lock操作锁定,则不允许执行unlock操作,也不允许unlock一个被其他线程锁定的变量。 + * (8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(store&write)。 + * + * + * 【效能优化】:Java内存模型没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,没有限制即时编译器进行调整代码执行顺序这类优化措施。 + * */ \ No newline at end of file -- Gitee From 02f84095dd4fd21b2018b1d9251724fe8a625e44 Mon Sep 17 00:00:00 2001 From: fyang21117 <1135783636@qq.com> Date: Fri, 27 Dec 2019 16:07:43 +0800 Subject: [PATCH 5/7] add week_03/22/Semaphore-22.md. --- week_03/22/Semaphore-22.md | 287 +++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 week_03/22/Semaphore-22.md diff --git a/week_03/22/Semaphore-22.md b/week_03/22/Semaphore-22.md new file mode 100644 index 0000000..5599b20 --- /dev/null +++ b/week_03/22/Semaphore-22.md @@ -0,0 +1,287 @@ +/* + * 【参考链接】 + * 2019年12月25日09:35:01 + * https://tool.oschina.net/uploads/apidocs/jdk-zh/java/util/concurrent/Semaphore.html + * + */ + +package java.util.concurrent; +import java.util.Collection; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; + +/** + * Semaphore类是一个计数信号量,必须由获取它的线程释放,通常用于限制可以访问某些资源(物理或逻辑的)线程数目,信号量控制的是线程并发的数量。 + * 计数器:一个信号量有且仅有3种操作,且它们全部是原子的:初始化、增加和减少 + * 增加可以为一个进程解除阻塞; + * 减少可以让一个进程进入阻塞。 + * + * 原理理解: + * Semaphore是用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。 + * 一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。 + * 如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。 + * + */ +public class Semaphore implements java.io.Serializable { + private static final long serialVersionUID = -3222578661600680210L; + /**通过AbstractQueuedSynchronizer子类的所有机制 */ + private final Sync sync; + + /** + * 信号量的同步实现。使用AQS状态表示许可证。分为公平和非公平版本。 + */ + abstract static class Sync extends AbstractQueuedSynchronizer { + private static final long serialVersionUID = 1192457210091910933L; + + Sync(int permits) { + setState(permits); + } + + final int getPermits() { + return getState(); + } + + final int nonfairTryAcquireShared(int acquires) { + for (;;) { + int available = getState(); + int remaining = available - acquires; + if (remaining < 0 || + compareAndSetState(available, remaining)) + return remaining; + } + } + + protected final boolean tryReleaseShared(int releases) { + for (;;) { + int current = getState(); + int next = current + releases; + if (next < current) // overflow + throw new Error("Maximum permit count exceeded"); + if (compareAndSetState(current, next)) + return true; + } + } + + final void reducePermits(int reductions) { + for (;;) { + int current = getState(); + int next = current - reductions; + if (next > current) // underflow + throw new Error("Permit count underflow"); + if (compareAndSetState(current, next)) + return; + } + } + + final int drainPermits() { + for (;;) { + int current = getState(); + if (current == 0 || compareAndSetState(current, 0)) + return current; + } + } + } + + /** + * 不公平版本 + */ + static final class NonfairSync extends Sync { + private static final long serialVersionUID = -2694183684443567898L; + + NonfairSync(int permits) { + super(permits); + } + + protected int tryAcquireShared(int acquires) { + return nonfairTryAcquireShared(acquires); + } + } + + /** + * 公平版本 + */ + static final class FairSync extends Sync { + private static final long serialVersionUID = 2014338818796000944L; + + FairSync(int permits) { + super(permits); + } + + protected int tryAcquireShared(int acquires) { + for (;;) { + if (hasQueuedPredecessors()) + return -1; + int available = getState(); + int remaining = available - acquires; + if (remaining < 0 || + compareAndSetState(available, remaining)) + return remaining; + } + } + } + + /** + * 创建具有给定许可数和非空公平设置的信号量。 + */ + public Semaphore(int permits) { + sync = new NonfairSync(permits); + } + + /** + * 创建具有给定许可数和给定公平性设置的信号量。 + */ + public Semaphore(int permits, boolean fair) { + sync = fair ? new FairSync(permits) : new NonfairSync(permits); + } + + /** + * 从这个信号量获取一个许可,阻塞直到一个可用,或者线程被中断。 + */ + public void acquire() throws InterruptedException { + sync.acquireSharedInterruptibly(1); + } + + /** + * 从这个信号量获取一个许可,阻塞直到一个可用为止。 + * 获得许可证,如果有许可证并立即返回,将可用许可证的数量减少一个。 + * + * 如果没有可用的许可证,则当前线程将被禁用以进行线程调度,并处于休眠状态, + * 直到其他某个线程调用此信号量的释放方法,并且下一个将为当前线程分配许可证。 + */ + public void acquireUninterruptibly() { + sync.acquireShared(1); + } + + /** + * 从该信号量获取一个许可证,前提是在调用时有一个许可证可用。 + * 获取许可证(如果有)并立即返回,值为true,将可用许可证的数量减少一个。 + * 如果没有可用的许可证,则此方法将立即返回值false。 + */ + public boolean tryAcquire() { + return sync.nonfairTryAcquireShared(1) >= 0; + } + + /** + * 从该信号量获取许可证,前提是在给定的等待时间内该信号量可用并且当前线程未被中断。 + * 获取许可证(如果有)并立即返回,值为真,将可用许可证数量减少一个。 + * 如果没有可用的许可证,则当前线程变为线程调度目的禁用,并处于休眠状态。 + */ + public boolean tryAcquire(long timeout, TimeUnit unit) + throws InterruptedException { + return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); + } + + /** + * 释放许可证,将其返回到信号灯。 + */ + public void release() { + sync.releaseShared(1); + } + + /** + * 从这个信号量获取给定数量的许可,阻塞直到所有可用,或者线程被中断。 + */ + public void acquire(int permits) throws InterruptedException { + if (permits < 0) throw new IllegalArgumentException(); + sync.acquireSharedInterruptibly(permits); + } + + /** + * 从这个信号量获取给定数量的许可,阻塞直到所有许可都可用。 + */ + public void acquireUninterruptibly(int permits) { + if (permits < 0) throw new IllegalArgumentException(); + sync.acquireShared(permits); + } + + /** + * 仅当调用时所有许可都可用时,才从该信号量获取给定数量的许可。 + */ + public boolean tryAcquire(int permits) { + if (permits < 0) throw new IllegalArgumentException(); + return sync.nonfairTryAcquireShared(permits) >= 0; + } + + /** + * 如果在给定的等待时间内所有许可都可用且当前线程未被中断,则从该信号量获取给定数量的许可。 + */ + public boolean tryAcquire(int permits, long timeout, TimeUnit unit) + throws InterruptedException { + if (permits < 0) throw new IllegalArgumentException(); + return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout)); + } + + /** + * 释放给定数量的许可,将其返回到信号量。 + */ + public void release(int permits) { + if (permits < 0) throw new IllegalArgumentException(); + sync.releaseShared(permits); + } + + /** + * 返回此信号量中当前可用的许可证数。 + */ + public int availablePermits() { + return sync.getPermits(); + } + + /** + * 获取并返回所有立即可用的许可证,如果有负许可证,则释放它们。 + * 返回后,可获得零张许可证。 + */ + public int drainPermits() { + return sync.drainPermits(); + } + + /** + * 通过指定的减少来减少可用许可证的数量。 + * 此方法在使用信号量跟踪不可用资源的子类中非常有用。 + * 这种方法与acquire的不同之处在于,它不阻止等待许可证可用。 + */ + protected void reducePermits(int reduction) { + if (reduction < 0) throw new IllegalArgumentException(); + sync.reducePermits(reduction); + } + + /** + * 如果此信号量的公平性设置为true,则返回true。 + */ + public boolean isFair() { + return sync instanceof FairSync; + } + + /** + * 查询是否有线程正在等待获取。 + * 请注意,由于取消可能随时发生,因此真正的返回并不保证任何其他线程将获得。 + * 此方法主要用于监视系统状态。 + */ + public final boolean hasQueuedThreads() { + return sync.hasQueuedThreads(); + } + + /** + * 返回等待获取的线程数的估计值。 + * 该值只是一个估计值,因为当此方法遍历内部数据结构时,线程数可能会动态更改。 + * 该方法设计用于监控系统状态,不用于同步控制。 + */ + public final int getQueueLength() { + return sync.getQueueLength(); + } + + /** + * 返回包含可能正在等待获取的线程的集合。 + * 由于实际的线程集在构造此结果时可能会动态更改,因此返回的集合只是一个最佳努力估计值。返回集合的元素没有特定的顺序。 + * 此方法旨在促进子类的构造,从而提供更广泛的监视设施。 + */ + protected Collection getQueuedThreads() { + return sync.getQueuedThreads(); + } + + /** + * 返回标识此信号量及其状态的字符串。 + * 括号中的状态包括字符串“permissions=”,后跟许可证数。 + */ + public String toString() { + return super.toString() + "[Permits = " + sync.getPermits() + "]"; + } +} -- Gitee From 75f3c07b2d9e1edfd2f72d21f727735e79aa6328 Mon Sep 17 00:00:00 2001 From: fyang21117 <1135783636@qq.com> Date: Fri, 27 Dec 2019 16:08:42 +0800 Subject: [PATCH 6/7] add week_03/22/synchronized-22.md. --- week_03/22/synchronized-22.md | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 week_03/22/synchronized-22.md diff --git a/week_03/22/synchronized-22.md b/week_03/22/synchronized-22.md new file mode 100644 index 0000000..6dd0996 --- /dev/null +++ b/week_03/22/synchronized-22.md @@ -0,0 +1,58 @@ +/** + * 【参考资料】 + * 《深入理解Java虚拟机》 + * 2019年12月24日15:44:20 + * + * 【关于互斥同步(Mutual Exclusion & Synchronization)】 + * 互斥同步是常见的一种并发正确性保障手段。 + * 同步是指多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个/一些(使用信号量时)线程使用。 + * 互斥是实现同步的一种手段(方法、原因),临界区、互斥量和信号量都是主要的互斥实现方式。 + * + * 【关于synchronized】 + * 在Java中,最基本的互斥同步手段就是synchronized关键字,synchronized关键字经过编译之后, + * 会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,都需要一个reference类型的参数来指明要锁定和解锁的对象。 + * 如果Java程序中synchronized明确指定了对象参数,那就是这个对象的reference; + * 如果没有明确规定,那就根据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或Class对象来作为锁对象。 + * + * 【注意】synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题; + * 其次,同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。 + */ + + + + /** + * 【参考资料】 + * 《Java核心技术 卷I 基础知识》 + * + * 【关于锁Lock】 + * (1)锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。 + * (2)锁可以管理试图进入被保护代码片段的线程。 + * (3)锁可以拥有一个或多个相关的条件对象。 + * (4)每个条件对象管理那些已经进入被保护的代码段但还不能运行的代码。 + * + * 【关于synchronized】 + * Java中的每一个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么对象的锁将会保护整个方法。 + * 要调用该方法,线程必须获得内部的对象锁。 + */ + + public synchronized void method(){ + //method body + } + //等价于 + public void method(){ + this.intrinsiclock.lock(); + try{ + //method body + } + finally{ + this.intrinsiclock.unlock(); + } + } + + + /*** + * 其他参考链接 + * 【Java并发编程之深入理解】Synchronized的使用:https://blog.csdn.net/zjy15203167987/article/details/82531772 + * 深入理解Java并发之synchronized实现原理:https://blog.csdn.net/javazejian/article/details/72828483 + * + */ \ No newline at end of file -- Gitee From 3525047bde9a83b507bc94023624e46ef0cc6e33 Mon Sep 17 00:00:00 2001 From: fyang21117 <1135783636@qq.com> Date: Fri, 27 Dec 2019 16:09:40 +0800 Subject: [PATCH 7/7] add week_03/22/VolatileTest-22.md. --- week_03/22/VolatileTest-22.md | 99 +++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 week_03/22/VolatileTest-22.md diff --git a/week_03/22/VolatileTest-22.md b/week_03/22/VolatileTest-22.md new file mode 100644 index 0000000..81d2c0e --- /dev/null +++ b/week_03/22/VolatileTest-22.md @@ -0,0 +1,99 @@ +/** + * 【参考资料】 + * 来源:《深入理解Java虚拟机》第十二章、《Java核心技术 卷I 基础知识》 + * 2019年12月24日10:19:44 + * + * + * 【含义】关键字volatile是Java虚拟机提供的最轻量级的同步机制。 + * 【作用】当一个变量定义为volatile之后, + * 保证此变量对所有线程的可见性:当一条线程修改了变量值,新值对其他线程是立即得知的。(普通变量值传递需要通过主内存来完成) + * volatile变量读操作的性能消耗与普通变量几乎没什么差别,但是写操作会慢一些,因为需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。 + * + * + * Java内存模型对volatile变量定义的【特殊规则】 + * 假定T表示一个线程,V和W分别表示两个volatile型变量,进行read、load、use、assign、store和write操作需要满足的规则 + * (1)只有当线程T对变量V执行的前一个动作是load,线程T才能对变量V执行use动作;只有T对V执行的后一个动作是use,T才能对V执行load动作。 + * 要求:在工作内存中,每次使用V前都必须先从主内存刷新最新的值,用于保证能看见其他线程对变量V所做的修改后的值。 + * + * (2)只有当线程T对变量V执行的前一个动作是assign,线程T才能对变量V执行store动作;只有T对V执行的后一个动作是store,T才能对V执行assign动作。 + * 要求:在工作内存中,每次修改V后都必须立刻同步回主内存中,用于保证其他线程可以看到自己对变量V所做的修改。 + * + * (3)假定动作A是线程T对变量V的use或assign动作,F是load或store动作,F是read或write动作; + * 动作B是线程T对变量W的use或assign动作,G是load或store动作,Q是read或write动作; + * 如果A先于B,那么P先于Q。 + * 要求:volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同。 + * + * + * 【警告】 + * Volatile变量不能提供原子性,不能保证读取、翻转和写入不被中断。 + * + * + * + * 其他参考链接 + * 全面理解Java内存模型(JMM)及volatile关键字:https://blog.csdn.net/javazejian/article/details/72772461 + * + * + * */ + + + + +/** + * 【变量自增运算测试】 + * 运行结果每次不同:112026,117029 and so on + * + * 原因:volatile变量只能保证可见性,还需要通过加锁来保证原子性(使用synchronized或java.util.concurrent原子类) + */ + public class VolatileTest{ + public static volatile int race = 0; + public static void increase(){ + race ++; + } + private static final int THREADS_COUNT = 20; + public static void main(String[] args) { + + Thread[] threads = new Thread[THREADS_COUNT]; + for(int i=0;i 1){ + Thread.yield(); + } + System.out.println(race); + } + } + + + +/** + * 【单例模式】 + * volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障, + * 这样,在它的赋值完成之前,就不用会调用读操作。(即重排序时不能把后面的指令重排序到内存屏障之前的位置) + * 注意:volatile阻止的不是singleton = newSingleton()的内部指令重排, + * 而是保证了在一个写操作完成之前,不会调用读操作(if (instance == null))。 + * + * 指令重排序:指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理。 + * 但不是指令任意重排,CPU需要能正确处理指令依赖情况以保障程序能得出正确的执行结果。 + */ + public class Singleton{ + private volatile static Singleton singleton = null; + private Singleton() { } + public static Singleton getInstance() { + if (singleton== null) { + synchronized (Singleton.class) { + if (singleton== null) { + singleton= new Singleton(); + } + } + } + return singleton; + } +} -- Gitee