公司的网站建设与维护,公司注册查重名,湛江网站制作系统,广告设计要学哪些软件目录
前言
一.锁策略
1.1乐观锁和悲观锁
⭐ 两者的概念
⭐实现方法
1.2读写锁 ⭐概念
⭐实现方法
1.3重量级锁和轻量级锁
1.4自旋锁和挂起等待锁
⭐概念
⭐代码实现
1.5公平锁和非公平锁
1.6可重入锁和不可重入锁
二.CAS
2.1为什么需要CAS
2.2CAS是什么
⭐CAS…目录
前言
一.锁策略
1.1乐观锁和悲观锁
⭐ 两者的概念
⭐实现方法
1.2读写锁 ⭐概念
⭐实现方法
1.3重量级锁和轻量级锁
1.4自旋锁和挂起等待锁
⭐概念
⭐代码实现
1.5公平锁和非公平锁
1.6可重入锁和不可重入锁
二.CAS
2.1为什么需要CAS
2.2CAS是什么
⭐CAS的介绍
⭐CAS工作原理
2.3CAS存在的问题
2.4CAS的应用
⭐实现原子类
⭐实现自旋锁
2.5CAS的缺点
三.JUC 3.1ReentrantLock类
3.2Semaphore类
3.3CountDownLatch类 个人主页tq02的博客_CSDN博客-C语言,Java,Java数据结构领域博主 本文由 tq02 原创首发于 CSDN 本章讲解内容多线程的策略锁、CAS和JUC 学习专栏 C语言 JavaSE MySQL基础 前言 在上章的 多线程二 中我们学习到为了线程安全我们需要进行加锁操作锁这个概念不仅仅只存在于Java当中锁也分很多种类。CAS在多线程二的讲解中稍微提及过至于JUC则是指java.util.concurrent的常见类。 一.锁策略 锁策略一共有10种在面试的过程当中也是非常重要的我们需要了解锁策略的每一种。
在面试当中你的面试官是会询问你的哦所以为了自己的大钱途努力学习吧
1.1乐观锁和悲观锁
⭐ 两者的概念
悲观锁: 总是假设最坏的情况每次去拿数据的时候都认为别人会修改所以每次在拿数据的时候都会上锁这样别人想拿这个数据就会阻塞直到它拿到锁。乐观锁假设数据一般情况下不会产生并发冲突所以在数据进行提交更新的时候才会正式对数据是否产生并发冲突进行检测如果发现并发冲突了则让返回用户错误的信息让用户决定如何去做。
抽象表达 悲观锁就相当于学生向老师提问但是学生认为老师不一定有空因此先发个信息给老师老师说有空则立马去解答如果没空(被其他线程加锁)则等待老师有空(解锁)。 乐观锁学生不认为老师很忙直接去询问老师结果2种老师如果有空则会解答没空就下次来询问。
⭐实现方法 悲观锁使用synchronized关键字来实现正常的加锁行为。 乐观锁添加一个版本号通过在进行数据更新操作时先读取数据并记录版本号然后在更新数据时检查版本号是否一致。如果版本号一致说明没有其他线程修改过数据可以进行更新如果版本号不一致说明其他线程已经修改过数据更新。 乐观锁Java代码实现
public class Counter {private int count 0;private int version 0; //版本号public void increment() { while (true) {int currentVersion version;if (compareAndSet(currentVersion)) {count;break;}}}public int getCount() {return count;}public synchronized boolean compareAndSet(int expectVersion) {if (version expectVersion) {version; //版本号相同时执行一次操作1return true;}return false; //版本号不同则返回false}}compareAndSet方法实现了基于版本号的乐观锁。increment方法先读取当前的版本号然后在一个while循环中不断尝试更新数据如果compareAndSet方法返回true则表示更新成功否则需要继续重试。 结论悲观锁通过加锁保护共享资源保证线程安全。乐观锁则通过无锁编程的方式提高并发性能。开发人员需要根据实践场景选择适应的锁。 1.2读写锁 ⭐概念 多线程之间数据的读取方之间不会产生线程安全问题但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁就会产生极大的性能损耗。所以读写锁因此而产生。 一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据. 两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.两个线程都要写一个数据, 有线程安全问题.一个线程读另外一个线程写, 也有线程安全问题 ⭐实现方法 读写锁将读操作和写操作区分对待而为了实现读写锁Java标准库提供了ReentrantReadWriteLock 类,在该类中又使用了2种类分别实现了读锁和写锁。
ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行 加锁解锁.ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进 行加锁解锁.
ReentrantReadWriteLock.ReadLock 类代码实现
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadLockDemo {private static ReentrantReadWriteLock lock new ReentrantReadWriteLock();private static ReentrantReadWriteLock.ReadLock readLock lock.readLock();public static void main(String[] args) {new Thread(ReadLockDemo::read).start();new Thread(ReadLockDemo::read).start();}public static void read() {try {readLock.lock(); //加锁System.out.println(Thread.currentThread().getName() 获取了读锁);// 执行读操作Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();} finally {readLock.unlock(); //解锁System.out.println(Thread.currentThread().getName() 释放了读锁);}}
}ReentrantReadWriteLock.WriteLock 类代码实现
import java.util.concurrent.locks.ReentrantReadWriteLock;public class MyReadWriteLock {private ReentrantReadWriteLock lock new ReentrantReadWriteLock();public void writeData() {ReentrantReadWriteLock.WriteLock writeLock lock.writeLock();writeLock.lock(); //加锁try {// 从文件或数据库中写入数据System.out.println(Thread.currentThread().getName() is writing data...);Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {writeLock.unlock(); //解锁}}
}结论读加锁和读加锁之间, 不互斥、写加锁和写加锁之间, 互斥、读加锁和写加锁之间, 互斥. 1.3重量级锁和轻量级锁 所谓的重量和轻量就是开销程度大和小。重量级锁加锁的开销比较大(花的时间多、占用系统资源多).轻量级锁加锁开销小(花的时间少、占用系统资源少。
重量级锁交给 OS 管理锁的争抢释放 CPU 资源ReentrantLock 类表示重量级锁
轻量级锁JVM 自己管理锁的争抢无锁自旋锁CPU资源不释放实现基于CAS。
注一个悲观锁可能是重量级锁、一个乐观锁可能是轻量锁 1.4自旋锁和挂起等待锁
⭐概念 自旋锁是一种典型的 轻量级锁 的实现方式. 优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁. 缺点: 如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源. 挂起等待锁是一种典型的 重量级锁 的实现方式 优点避免线程的空轮询确保在锁被释放后立即获取到锁可以避免不必要的自旋浪费CPU资源。 缺点增加了系统资源消耗和线程的等待时间。 ⭐代码实现
自旋锁的代码实现
import java.util.concurrent.atomic.AtomicReference;public class SpinLock {private AtomicReferenceThread lock new AtomicReference();public void lock() {Thread currentThread Thread.currentThread();while (!lock.compareAndSet(null, currentThread)) {// 自旋等待}}public void unlock() {Thread currentThread Thread.currentThread();lock.compareAndSet(currentThread, null);}
}挂起等待锁代码实现
public class WaitLockExample {private final Object lock new Object();private boolean isLocked false;public void foo() throws InterruptedException {synchronized(lock) {while(isLocked) { //挂起等待中lock.wait();}// 执行线程的操作isLocked true;}}public void bar() {synchronized(lock) {// 执行线程的操作isLocked false;lock.notify();}}
}1.5公平锁和非公平锁 公平锁遵循先来后到的原则例如线程A、B、C依次来当A释放锁时按顺序则下一个加锁的线程为B。 非公平锁不遵守先来后到的原则例如:线程A、B、C依次来当A释放锁时结果下一个加锁的线程为C而不是B
注操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是非公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序.synchronized 是非公平锁.
1.6可重入锁和不可重入锁 可重入锁“可以重新进入的锁”即允许同一个线程多次获取同一把锁。
情况递归函数里有加锁操作递归过程中这个锁会阻塞自己吗不会那么这个锁就是可重入锁而若是发生阻塞那么自己阻塞自己无法解锁导致了死锁。
Java中以Reentrant开头命名的锁都是可重入锁而且JDK提供的所有现成的Lock实现类包括synchronized关键字锁都是可重入的。 不可重入锁只判断这个锁有没有被锁上只要被锁上申请锁的线程都会被要求等待。实现简单 二.CAS
2.1为什么需要CAS 多个线程同时访问锁那么一些线程将被挂起当线程恢复执行时必须等待其它线程执行完他们的时间片以后才能被调度执行在挂起和恢复执行过程中存在着很大的开销。锁还存在着其它一些缺点当一个线程正在等待锁时它不能做任何事。如果一个线程在持有锁的情况下被延迟执行那么所有需要这个锁的线程都无法执行下去。如果被阻塞的线程优先级高而持有锁的线程优先级低将会导致优先级反转。 CAS可以解决这一类弊端鉴别线程冲突一旦检测到冲突就重复当前操作直到没有冲突为止。与锁相比CAS会使得程序设计比较复杂但是由于其天生免疫死锁根本就没有锁当然就不会有线程一直阻塞了更为重要的是使用无锁的方式没有锁竞争带来的开销也没有线程间频繁调度带来的开销他比基于锁的方式有更优越的性能所以在目前已经被广泛应用。
2.2CAS是什么
⭐CAS的介绍 CAS机制全称compare and swap翻译为比较并交换是一种有名的无锁lock-free算法。只有一步原子操作所以非常快。而且CAS避免了请求操作系统来裁定锁的问题直接在CPU内部就完成了。
CAS工作伪代码真实的 CAS 是一个原子的硬件指令完成的. 这个伪代码只是辅助理解 CAS是由CPU支持的原子操作其原子性是在硬件层面进行保证的。
注一个线程的CAS先访问到内存另一个后访问内存。 ⭐CAS工作原理
CAS包含3个值
需要读写的内存位置(V)原来的值(A)期待更新的值(B)。
CAS操作逻辑如下如果内存位置V的值等于预期的A值则将该位置更新为新值B否则不进行任何操作。许多CAS的操作是自旋的如果操作不成功会一直重试直到操作成功为止。 2.3CAS存在的问题 ABA问题 因为CAS会检查旧值有没有变化因此存在一个问题。比如一个旧值A变为了成B然后再变成A刚好在做CAS时检查发现旧值并没有变化依然为A但是实际上的确发生了变化。解决方案沿袭数据库中常用的乐观锁方式添加一个版本号可以解决。原来的变化路径A-B-A就变成了1A-2B-3C。Java 1.5后的atomic包中提供了AtomicStampedReference来解决ABA问题解决思路就是这样的。 自旋时间过长 使用CAS时非阻塞同步也就是说不会将线程挂起会自旋无非就是一个死循环进行下一次尝试如果这里自旋时间过长对性能是很大的消耗。如果JVM能支持处理器提供的pause指令那么在效率上会有一定的提升。 只能保证一个共享变量的原子操作 当对一个共享变量执行操作时CAS能保证其原子性如果对多个共享变量进行操作CAS就不能保证其原子性。 解决方案利用对象整合多个共享变量即一个类中的成员变量就是这几个共享变量。然后将这个对象做CAS操作就可以保证其原子性。atomic中提供了AtomicReference来保证引用对象之间的原子性。 2.4CAS的应用
⭐实现原子类
Java标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的. 典型的就是 AtomicInteger 类. 其中的 getAndIncrement 相当于 i 操作
AtomicInteger atomicInteger new AtomicInteger(0);atomicInteger.getAndIncrement(); // iatomicInteger.incrementAndGet(); //iatomicInteger.getAndDecrement(); //i--atomicInteger.decrementAndGet(); //--i
代码示例
private static AtomicInteger count new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() - {for (int i 0; i 50000; i) {// countcount.getAndIncrement(); }});Thread t2 new Thread(() - {for (int i 0; i 50000; i) {count.getAndIncrement();}});t1.start();t2.start();System.out.println(count.get()); //输出结果100000}
如上述代码此时就不会存在相加时覆盖相同的值了因此结果为100000.
⭐实现自旋锁 基于 CAS 实现更灵活的锁, 获取到更多的控制权.
自旋锁的代码实现
public class SpinLock {
private Thread owner null;
public void lock(){// 通过 CAS 看当前锁是否被某个线程持有.// 如果这个锁已经被别的线程持有, 那么就自旋等待.// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.while(!CAS(this.owner, null, Thread.currentThread())){}}public void unlock (){this.owner null;}
}
通过CAS判定出当前变量的自增过程当中是否有其他线程穿插进来了。 2.5CAS的缺点
一次性只能保证一个共享变量的原子性 当对一个共享变量执行操作时我们可以使用循环CAS的方式来保证原子操作但是对多个共享量操作时循环CAS就无法保证操作的原子性这个时候就可以用锁来保证原子性。循环会耗时 我们可以看到getAndAddInt方法执行时如果CAS失败会一直进行尝试。如果CAS长时间一直不成功可能会给CPU带来很大的开销。
在并发冲突概率大的高竞争环境下如果CAS一直失败会一直重试CPU开销较大。针对这个问题的一个思路是引入退出机制如重试次数超过一定阈值后失败退出。当然更重要的是避免在高竞争环境下使用乐观锁。 3.存在ABA问题 三.JUC JUC工具包全名:java.util.concurrent专门处理线程的工具包从jdk1.5开始出现。
目的为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题
而JUC中常见的类有ReentrantLock 可重入锁Semaphore 信号量 ountDownLatch 计数器 CyclicBarrier 循环屏障。 3.1ReentrantLock类 ReentrantLock类可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全. // ReentrantLock 的构造方法
public ReentrantLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync();
}
用法
lock(): 加锁, 如果获取不到锁就死等trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁unlock(): 解锁。
与synchronize的区别
synchronized 是一个关键字, 是 JVM 内部实现的(大概率是基于 C 实现). ReentrantLock 是标准库的一个类, 在 JVM 外实现的(基于 Java 实现).synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活,但是也容易遗漏 unlock.synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃.synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式
那么如何选择哪个锁呢
锁竞争不激烈的时候, 使用 synchronized, 效率更高, 自动释放更方便.锁竞争激烈的时候, 使用 ReentrantLock, 搭配 trylock 更灵活控制加锁的行为, 而不是死等.如果需要使用公平锁, 使用 ReentrantLock.
3.2Semaphore类 Semaphore类信号量 用来表示 “可用资源的个数”本质上就是一个计数器。该类用于控制信号量的个数构造时传入个数。总数就是控制并发的数量。 抽象解释五双筷子A拿了一双则显示还有4双可用A放回则显示还有5双可用。若是5双筷子都被别人拿了则禁止别人拿取筷子等待别人放回。
import java.util.concurrent.Semaphore;public class SemaphoreTest {public static void main(String[] args) {// 创建Semaphore对象设置许可数为3Semaphore semaphore new Semaphore(3);// 创建10个线程for (int i 1; i 10; i) {MyThread thread new MyThread(semaphore, i);new Thread(thread).start();}}static class MyThread implements Runnable {private Semaphore semaphore;private int threadNum;public MyThread(Semaphore semaphore, int threadNum) {this.semaphore semaphore;this.threadNum threadNum;}Overridepublic void run() {try {// 获取许可若还有许可数则占用若无则堵塞semaphore.acquire();System.out.println(线程 threadNum 获取到了许可);Thread.sleep(2000); // 模拟线程执行一段耗时的操作} catch (InterruptedException e) {e.printStackTrace();} finally {// 释放许可semaphore.release();System.out.println(线程 threadNum 释放了许可);}}}}当一个线程调用 acquire() 方法时计数器就会减一当计数器为0时它就会阻塞。当一个线程调用 release() 方法时它将增加计数器的值然后唤醒一个被阻塞的线程。 3.3CountDownLatch类
计数器同时等待 N 个任务执行结束。例如田径比赛只有所有人都通过终点才能公布成绩。
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {// 创建计算器CountDownLatch countDownLatch new CountDownLatch(5);// 创建线程池ExecutorService service Executors.newFixedThreadPool(5);// 创建新线程执行任务for (int i 1; i 5; i) {service.submit(() - {Thread currThread Thread.currentThread();System.out.println(currThread.getName() 开始起跑);int runTime new Random().nextInt(5) 1;try {TimeUnit.SECONDS.sleep(runTime);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(currThread.getName() 到达终点用时 runTime);countDownLatch.countDown(); //在 CountDownLatch 内部的计数器同时自减.});}countDownLatch.await(); //阻塞等待所有任务执行完毕System.out.println(比赛结果宣布);}
}在代码当中只有线程全部结束时才能公布最后的结果