Juc(常用工具,CAS算法) |
您所在的位置:网站首页 › juc工具 › Juc(常用工具,CAS算法) |
Java中的管程(了解)
我们这里就说一下ObjectMonitor中几个关键字段的含义: _count:记录owner线程获取锁的次数。这句话很好理解,这也决定了synchronized是可重入的。 _owner:指向拥有该对象的线程 _WaitSet:存放处于wait状态的线程队列。 _EntryList:存放等待锁而被block的线程队列。 _count和_owner很好理解,后面两个队列 总结 Java对象与monitor的关联:通过在Java对象头中的mark word中存储了指向monitor的指针。 明白了Java中的管程ObjectMonitor的工作原理,知道了Java语法中的wait/notify/notifyAll方法底层都是调用了ObjectMonitor的方法。 明白了管程的概念,一种程序结构,封装了同步操作,避免直接使用PV信号量。。 管程管程(monitor),直译为“监视器”,是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行,所说“锁” JVM中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程(monitor)对象,管程(monitor)会随着java对象一同创建和销毁 执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方法在执行时候会持有管程,其他线程无法再获取同一个管程 Java中类似于管程的机制:Synchronized JDK 1.6引入了偏向锁、轻量级锁、适应性自旋、锁粗化 想要获取monitor的线程先进入monitor的_EntryList队列阻塞等待。即遇到synchronized关键字时。 如果monitor的_owner为空,则从队列中移出并赋值与_owner。 如果在程序里调用了wait()方法,则该线程进入_WaitSet队列。注意wait方法我们之前讲过,它会释放monitor锁,即将_owner赋值为null并进入_WaitSet队列阻塞等待。这时其他在_EntryList中的线程就可以获取锁了。 当程序里其他线程调用了notify/notifyAll方法时,就会唤醒_WaitSet中的某个线程,这个线程就会再次尝试获取monitor锁。如果成功,则就会成为monitor的owner。 当程序里遇到synchronized关键字的作用范围结束时,就会将monitor的owner设为null,退出。 引入管程的原因 信号量机制的缺点:进程自备同步操作,P(S)和V(S)操作大量分散在各个进程中,不易管理,易发生死锁。 管程特点:管程封装了同步操作,对进程隐蔽了同步细节,简化了同步功能的调用界面。 简单地说管程就是一个概念,任何语言都可以实现。目的就是为了简化同步调用的过程。 回想一下刚才我们说的Java中的实现ObjectMonitor。它具体干了什么事,其实就是说管程需要干哪些事。 JUC常用工具 CountDownLatch: 减少计数CyclicBarrier: 循环栅栏Semaphore: 信号灯 CountDownLatch Java 5.0 在java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。CountDownLatch 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。闭锁CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句。 CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行 案例 import java.util.concurrent.CountDownLatch; /* * CountDownLatch :闭锁,在完成某些运算是,只有其他所有线程的运算全部完成,当前运算才继续执行 */ public class TestCountDownLatch { public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(50); LatchDemo ld = new LatchDemo(latch); long start = System.currentTimeMillis(); for (int i = 0; i latch.await(); } catch (InterruptedException e) { } long end = System.currentTimeMillis(); System.out.println("耗费时间为:" + (end - start)); } } class LatchDemo implements Runnable { private CountDownLatch latch; public LatchDemo(CountDownLatch latch) { this.latch = latch; } @Override public void run() { try { for (int i = 0; i System.out.println(i); } } } finally { latch.countDown(); } } } 源码countDownLatch类中只提供了一个构造器: //参数count为计数值 public CountDownLatch(int count) { };类中有三个方法是最重要的: //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行 public void await() throws InterruptedException { }; //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; //将count值减1 public void countDown() { }; 循环栅栏CyclicBarrierCyclicBarrier看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier一次障碍数会加一,如果达到了目标障碍数,才会执行cyclicBarrier.await()之后的语句。可以将CyclicBarrier理解为加1操作 //集齐7颗龙珠就可以召唤神龙 public class CyclicBarrierDemo { //创建固定值 private static final int NUMBER = 7; public static void main(String[] args) { //创建CyclicBarrier CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()->{ System.out.println("*****集齐7颗龙珠就可以召唤神龙"); }); //集齐七颗龙珠过程 for (int i = 1; i try { System.out.println(Thread.currentThread().getName()+" 星龙被收集到了"); //等待 cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } },String.valueOf(i)).start(); } } } 信号灯SemaphoreSemaphore的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用acquire方法获得许可证,release方法释放许可 //6辆汽车,停3个车位 public class SemaphoreDemo { public static void main(String[] args) { //创建Semaphore,设置许可数量 Semaphore semaphore = new Semaphore(3); //模拟6辆汽车 for (int i = 1; i try { //抢占 semaphore.acquire(); System.out.println(Thread.currentThread().getName()+" 抢到了车位"); //设置随机停车时间 TimeUnit.SECONDS.sleep(new Random().nextInt(5)); System.out.println(Thread.currentThread().getName()+" ------离开了车位"); } catch (InterruptedException e) { e.printStackTrace(); } finally { //释放 semaphore.release(); } },String.valueOf(i)).start(); } } } CAS 算法 CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。CAS 是一种无锁的非阻塞算法的实现。CAS 包含了3 个操作数: 需要读写的内存值V进行比较的值A拟写入的新值B 当且仅当V 的值等于A 时,CAS 通过原子方式用新值B 来更新V 的值,否则不会执行任何操作。 案例: /* * 模拟 CAS 算法 */ public class TestCompareAndSwap { public static void main(String[] args) { final CompareAndSwap cas = new CompareAndSwap(); for (int i = 0; i @Override public void run() { int expectedValue = cas.get(); boolean b = cas.compareAndSet(expectedValue, (int) (Math.random() * 101)); System.out.println(b); } }).start(); } } } class CompareAndSwap { private int value; // 获取内存值 public synchronized int get() { return value; } // 比较 public synchronized int compareAndSwap(int expectedValue, int newValue) { int oldValue = value; if (oldValue == expectedValue) { this.value = newValue; } return oldValue; } // 设置 public synchronized boolean compareAndSet(int expectedValue, int newValue) { return expectedValue == compareAndSwap(expectedValue, newValue); } } true false false false false ... 原子变量类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类。 类AtomicBoolean、AtomicInteger、AtomicLong 和AtomicReference 的实例各自提供对相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。 AtomicIntegerArray、AtomicLongArray 和AtomicReferenceArray 类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。 核心方法:boolean compareAndSet(expectedValue, updateValue) java.util.concurrent.atomic 包下提供了一些原子操作的常用类: AtomicBoolean 、AtomicInteger 、AtomicLong 、AtomicReferenceAtomicIntegerArray 、AtomicLongArrayAtomicMarkableReferenceAtomicReferenceArrayAtomicStampedReference 案例 import java.util.concurrent.atomic.AtomicInteger; /* * 一、i++ 的原子性问题:i++ 的操作实际上分为三个步骤“读-改-写” * int i = 10; * i = i++; //10 * * int temp = i; * i = i + 1; * i = temp; * * 二、原子变量:在 java.util.concurrent.atomic 包下提供了一些原子变量。 * 1. volatile 保证内存可见性 * 2. CAS(Compare-And-Swap) 算法保证数据变量的原子性 * CAS 算法是硬件对于并发操作的支持 * CAS 包含了三个操作数: * ①内存值 V * ②预估值 A * ③更新值 B * 当且仅当 V == A 时, V = B; 否则,不会执行任何操作。 */ public class TestAtomicDemo { public static void main(String[] args) { AtomicDemo ad = new AtomicDemo(); for (int i = 0; i // private volatile int serialNumber = 0; private AtomicInteger serialNumber = new AtomicInteger(0); @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { } System.out.println(getSerialNumber()); } public int getSerialNumber(){ return serialNumber.getAndIncrement(); } } 1 9 6 3 7 8 .... |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |