当前位置:网站首页>多线程进阶(锁策略,自旋+CAS,Synchronized,JUC,信号量)
多线程进阶(锁策略,自旋+CAS,Synchronized,JUC,信号量)
2022-07-30 05:48:00 【HDLaZy】
1:锁策略
锁策略:多线程对共享变量的操作,存在线程冲突的安全问题,引入锁策略解决这种问题。
- 乐观锁VS悲观锁
悲观锁:以悲观的心态看待线程冲突的问题,总是认为有其它线程要操作同一个共享变量,每次操作前都加锁乐观锁:以乐观的心态看待线程冲突的问题,总是认为没有线程会操作同一个共享变量,每次操作都不加锁直接操作共享变量(程序层面不加锁,CPU和操作系统实际有加锁)
- 自旋锁
- 循环,结合CAS不断尝试修改变量,直到修改成功才退出循环。
- 读写锁
- Java中的读写锁:
ReentrantReadWriteLock - 获取读锁:
reentrantReadWriteLock.readLock() - 获取写锁:
reentrantReadWriteLock.writeLock() - 写操作加/放锁:
writeLock.lock(); writeLock.unlock() - 读操作加/放锁:
readLock.lock(); readLock.unlock()
- Java中的读写锁:
- 公平锁VS非公平锁
非公平锁:竞争的方式获取锁- 优点:效率更高不考虑执行顺序
- 缺点:可能出现线程饥饿(某些线程得不到执行)的现象
公平锁:以申请锁的顺序来获取锁- 优点:公平
- 缺点:效率差
- 可重入锁VS不可不可重入锁
- Java中基本上都是可重入锁:lock,synchronized
- 一个线程是否可以多次获取同一个锁,就是可重入锁
- 重量级锁VS轻量级锁
重量级锁代价大轻量级锁代价小
- 独占锁VS共享锁
独占锁:Synchronized,ReentrantLock都属于独占锁共享锁:多个线程使用的锁,满足一定条件,就可以并发并行的执行,不满足条件则等待
2:CAS实现乐观锁,自旋锁
Cas:Compare and Swap(比较和交换):线程安全的无锁操作,尝试修改(成功/失败)
public boolean CompareAndSwap (修改一个变量的值){
尝试修改操作(不会有线程安全问题)
return 尝试修改的结果
}

自旋锁:
do{
获取变量值
}while(!Compare and Swap(变量值)){
尝试修改变量的值:如果修改成功则退出循环向下执行
:如果修改失败则再次循环
}
CAS存在问题:自旋+CAS实现的乐观锁,在修改变量的值时需要判断:
- 如果从内存中获取的值,和即将修改时再次获取的值一样则可以修改,反之操作失败,等待下一次消费
- 这样的问题为ABA问题,如果两次修改,值又变为原来的值,则会认为没有被修改从而出现问题,如反复充值等
- 引入版本号来解决这一问题,修改前和修改后比较版本号,版本号一致则修改,不一致不修改
如何解决ABA问题:引入版本号,不仅比较值,还要比较版本号。
AtomicStampedReference<V> : java提供的类通过版本号解决aba问题
AtomicReference和AtomicStampedReference是JUC下的atomic包提供的原子类:
AtomicReference不考虑版本号,会发生ABA问题AtomicStampedReference考虑版本号,不会发生ABA问题
模拟三线程为一个账号充值20元且只充值一次,当此账号不足20充值20,一个人消费此账户,初始余额19元,每次消费10元。
如果使用
AtomicReference即会发生ABA问题,当充值后,用户两次消费后余额又回到19元,则会进行重复充值。使用
AtomicStampedReference不会产生重复充值操作,即使金额相同,版本号却不同。public class AtomicStampedReferenceTest { //初始金额19,版本号0 private static AtomicStampedReference<Integer> money=new AtomicStampedReference<>(19,0); public static void main(String[] args) { for (int i = 0; i < 3; i++) { //获取版本号 int stamp = money.getStamp(); new Thread(new Runnable() { @Override public void run() { //系统一直运作 while (true){ //自旋,充值成功或者不满足充值要求退出 while (true){ //获取金额 Integer reference = money.getReference(); //小于20进行充值 if(reference<20){ //金额+20,版本号+1 if(money.compareAndSet(reference, reference+20, stamp,stamp+1)){ System.out.println("充值20元成功,余额为:"+money.getReference()); break;//充值成功退出 } }else { break;//不满足充值要求退出 } } } } }).start(); } //消费进程 new Thread(new Runnable() { @Override public void run() { //一直消费 while (true){ Integer reference = money.getReference(); if(reference>10){ money.compareAndSet(reference, reference-10, money.getStamp(), money.getStamp()+1); System.out.println("账户余额为:"+money.getReference()); }else { System.out.println("账户余额不足,余额为:"+money.getReference()); break; } } } }).start(); } }
3:Synchronized
- 以对象头加锁的方式,实现线程安全:申请同一个对象锁时,多个线程是同步互斥的
- 对象存在于堆中,且有一个对象头,其中就有一个状态的字段表示具体锁类型(无锁,偏向锁,轻量级锁,重量级锁)
- Synchronized申请锁成功,是后面的三种锁之一(偏向锁,轻量级锁,重量级锁)
字节码层面的Synchronized操作:
- Synchronized被编译为字节码文件后,里面存在一个对象头的monitor机制(同步监视器)
- monitor机制也存在计数器,一个线程可以反复申请一个锁,申请成功一次,计数器加1
- Synchroniezd----->monitorenter
- 执行同步代码
- 正常/异常都会释放锁----->monitorexit(存在两个)
try{
加锁Synchronized----->monitorenter
同步代码
释放锁----->monitorexit
}catch(Exception e){
释放锁----->monitorexit
}
JVM对Synchroniezd的优化:对象头的锁升级
- 无锁:
- 偏向锁:只有一个线程申请对象锁,就可以使用偏向锁,因为代价小,同一个线程可重入的方式多次申请同一个对象锁,也使用偏向锁。
- 轻量级锁:自旋+CAS 线程冲突不严重的时候相对重量级锁代价低。
- 重量级锁:基于系统级别的mutex lock 锁,代价巨大的系统加锁,当线程冲突严重时则升级为重量级锁。
其它锁优化:
锁消除:一个对象,不可能有其它线程持有引用就是线程安全的,可以不加锁
//StringBuffer是线程安全的,它的方法都是同步方法 @Override @HotSpotIntrinsicCandidate public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } //此时StringBuffer为局部变量,不存在其它线程的竞争 public static void main(String[] args) { //StringBuffer线程安全 StringBuffer stringBuffer=new StringBuffer(); //如果没有锁消除,则append方法会一直频繁加锁放锁,造成资源消耗 stringBuffer.append("锁"); stringBuffer.append("消"); stringBuffer.append("除"); System.out.println(stringBuffer.toString()); }
锁粗化:共享变量可能会被其它线程持有,但不停的执行加锁/放锁操作,会被优化为锁粗化
//共享变量 private static int count=0; public static void main(String[] args) throws InterruptedException { Runnable runnable=new Runnable() { @Override public void run() { //频繁的加锁释放锁的操作,会被优化 //申请锁,执行for循环完成后,释放锁 for(int i=0;i<1000;i++){ synchronized (this){ count++; } } } }; for(int i=0;i<2;i++){ new Thread(runnable).start(); } }
4:JUC并发包
Java并发包:Java.Util.Concurrent,提供多线程安全的且效率高的API
原子性包Java.Util.Concurrent.Atomic
AtomicInteger提供线程安全的原子操作
int getAndDecrement() n-- int getAndIncrement() n++ int incrementAndGet() ++n int decrementAndGet() --n int intValue() 返回当前值
原子性并发包:执行这些操作无需加锁,底层原理为CAS+自旋
private static AtomicInteger atomicInteger=new AtomicInteger(); public static void main(String[] args) throws InterruptedException { for(int i=0;i<10;i++){ new Thread(new Runnable() { @Override public void run() { for(int i=0;i<100;i++){ atomicInteger.getAndIncrement(); } } }).start(); } Thread.sleep(4000); System.out.println(atomicInteger.intValue()); }
AtomicReference和AtomicStampedReferenceAtomicReference:构造方法:
public AtomicReference(V initialValue) { value = initialValue; }
compareAndSet:
public final boolean compareAndSet(V expectedValue, V newValue) { return VALUE.compareAndSet(this, expectedValue, newValue); }
AtomicStampedReference:构造方法:
public AtomicStampedReference(V initialRef, int initialStamp) { pair = Pair.of(initialRef, initialStamp); }
getStamp(返回当前版本号):
public int getStamp() { return pair.stamp; }
getReference(返回当前值):
public V getReference() { return pair.reference; }
compareAndSet:
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
在CAS和自旋中容易出现ABA问题,使用
AtomicStampedReference会引入版本号,避免了ABA问题。
Jva.Util.Concurrent.locks:Lock体系

ReentrantLock:构造方法:
独占锁,可重入锁,根据构造方法提供的boolean类型参数决定公平或者非公平锁(无参构造器为非公平锁,flase也为非公平锁,true为公平锁)
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
加锁/释放锁:
lock():
public void lock() { sync.acquire(1); }unlock():
public void unlock() { sync.release(1); }
实例:private static Lock lock=new ReentrantLock(); private static int count=0; public static void main(String[] args) throws InterruptedException { for(int i=0; i<10;i++){ new Thread(new Runnable() { @Override public void run() { lock.lock(); for(int i=0;i<100;i++){ count++; } lock.unlock(); } }).start(); } Thread.sleep(2000); System.out.println(count); }
ReentrantReadWriteLock:构造方法:
public ReentrantReadWriteLock() { this(false); } public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); }
获取写锁或者读锁:
public static class WriteLock implements Lockpublic static class ReadLock implements Lock
实例:private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock(); private static ReentrantReadWriteLock.WriteLock writeLock=null; private static ReentrantReadWriteLock.ReadLock readLock=null; private static int count=0; static { writeLock = readWriteLock.writeLock(); readLock=readWriteLock.readLock(); } public static void main(String[] args) throws InterruptedException { //写操作 Runnable runnableWrite=new Runnable() { @Override public void run() { writeLock.lock(); for(int i=0;i<100;i++){ count++; } writeLock.unlock(); } }; for(int i=0;i<10;i++){ new Thread(runnableWrite).start(); } //读操作 Runnable runnableRead=new Runnable() { @Override public void run() { readLock.lock(); System.out.println(count); readLock.unlock(); } }; for(int i=0;i<10;i++){ new Thread(runnableRead).start(); } }
5:信号量Semaphore
Semaphore: 表示可用资源数,PV操作是原子操作,多线程环境直接使用。
构造方法:
//初始资源数目 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 acquire(int permits) throws InterruptedException { if (permits < 0) throw new IllegalArgumentException(); sync.acquireSharedInterruptibly(permits); }释放:
//释放一个资源 public void release() { sync.releaseShared(1); } //释放指定数目资源 public void release(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.releaseShared(permits); }
使用场景:
有限资源的并发执行
private static Semaphore semaphore=new Semaphore(5,true); public static void main(String[] args) { for(int i=0;i<20;i++){ final int j=i; new Thread(new Runnable() { @Override public void run() { try { semaphore.acquire(); System.out.println(j); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(); } } }).start(); } }多线程并发执行,执行顺序
private static Semaphore semaphore=new Semaphore(0); public static void main(String[] args) { Thread thread1=new Thread(new Runnable() { @Override public void run() { System.out.println("我是第一步执行的代码"); semaphore.release(); } }); thread1.start(); Thread thread2=new Thread(new Runnable() { @Override public void run() { try { semaphore.acquire(1); System.out.println("我是第二步执行的代码"); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(2); } } }); thread2.start(); Thread thread3=new Thread(new Runnable() { @Override public void run() { try { semaphore.acquire(2); System.out.println("我是第三步执行的代码"); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(2); } } }); thread3.start(); }
边栏推荐
猜你喜欢

PC DBCO-PEG3-Biotin|PC-生物素-PEG3-DBCO可用于使用无铜点击化学

VsCode connects to the remote server and modifies the file code

Biotin-NHS LC(72040-63-2)生物素接头|站点特定探针

【Exhibition of some projects】

js advanced study notes (detailed)

快速开发 GraphScope 图分析应用

instantDrag for Maya脚本 (移动模型时沿目标模型移动)

51数码管显示

OP 代币和不可转让的 NFT 致力于建立新的数字民主

迷宫问题----经典回溯法解决
随机推荐
使用 Helm 部署 GraphScope
测试第二题
ParseException line 8:13 mismatched input ‘(‘ expecting ) near ‘int‘ in create table statement
Unity Gizmos扩展:线框圆
图扑软件数字孪生民航飞联网,构建智慧民航新业态
Biotin-PEG4-DADPS-Picolyl-azide(CAS:2599839-59-3)生物素试剂
VsCode connects to the remote server and modifies the file code
GCD的定时器
Shortcut keys commonly used in the use of Word
The most complete difference between sizeof and strlen, as well as pointer and array operation analysis
DADPS Biotin Azide( CAS:1260247-50-4生物素基团和叠氮基团的 PEG 衍生物
如何将matlab数据导入modelsim仿真
GAIA-IR: Parallelized Graph Query Engine on GraphScope
How to save modelsim simulation data as a file
洛谷一P1097 [NOIP2007 提高组] 统计数字
Biotin-PEG4-SS-Alkyne_1260247-54-8_生物素-PEG4-SS-炔烃
---------手撕二叉树,完成二叉树的前中后序遍历,以及前中后序查找
The Force Plan Microservices | Centralized Configuration Center Config Asymmetric Encryption and Security Management
Interactively compose graphs in GraphScope based on the JupyterLab plugin
Cas 80750-24-9,去硫代生物素 N-羟基琥珀酰亚胺,淡黄色固体
