网站规划管理系统,京东网站开发需求,手机网站页面设计,温州外贸网站建设文章目录 悲观/乐观锁挂起等待锁/自旋锁偏向锁轻量级/重量级锁锁升级CASCAS引发的ABA问题解决方案 原子类 公平/不公平锁可重入锁ReentrantLock读写锁 Callable接口 这里的“悲观”“乐观”“挂起等待”“自旋”“轻量级”“重量级”“公平”“非公平”“可重入”仅代表某个锁的… 文章目录 悲观/乐观锁挂起等待锁/自旋锁偏向锁轻量级/重量级锁锁升级CASCAS引发的ABA问题解决方案 原子类 公平/不公平锁可重入锁ReentrantLock读写锁 Callable接口 这里的“悲观”“乐观”“挂起等待”“自旋”“轻量级”“重量级”“公平”“非公平”“可重入”仅代表某个锁的特性不具体指某个锁。 悲观/乐观锁
悲观锁总是在考虑最坏的情况认为在拿数据时总是存在线程不安全问题总是认为别人在同时修改这个数据于是每次拿的时候给这个数据上锁让其他线程拿不了直到锁释放。代表有synchronized、ReentrantLock。重量级锁有悲观锁的影子 乐观总是考虑最好的情况认为总是不存在有线程在同时修改的情况放心的去拿数据但是乐观锁有CAS的机制如果要进行更新操作发现读取到更新之间有其他线程修改了这个数据则会放弃修改重新读取再修改直到成功。代表有原子类原子类底层就是不断的CAS。轻量级锁、偏向锁都有乐观锁的影子
挂起等待锁/自旋锁
自旋锁线程未获得锁则不断尝试获取锁。可以及时获得锁CPU空转忙等但缺点是可能会很消耗CPU资源因此自旋到一定条件设定时间、次数就不自旋了。
while(抢锁失败){ 抢;if(!自旋条件) break;
}挂起等待锁线程未获得锁则进入阻塞状态等待被唤醒。不会很消耗CPU资源但缺点是等待周期可能会很长不能及时获得锁。
偏向锁
当同步代码块只有一个线程运行时可以理解为只有一个线程参与锁竞争此时并不会给这个线程加锁而是给这个线程一个标记ID由JVM记录这个机制也实现了Java的可重入锁方便理解可参考我写的解决死锁段落当下一次进入同步代码块时根据这个ID判断是不是仍然还是这个线程如果是则直接运行否则偏向锁升级为轻量级锁锁持有者为获取锁的线程其他线程自旋。 上面的过程有点抽象总结来说只有一个线程执行同步代码块的时候此刻加的锁为偏向锁如果发生其他线程同时竞争不激烈来竞争锁则锁升级为轻量级锁如果竞争再激烈则升级为重量级锁。
轻量级/重量级锁
轻量级锁锁竞争不激烈的场景下线程未获得锁则保持自旋不阻塞等待一直尝试获取锁。 重量级锁锁竞争激烈的场景下线程未竞到锁则进入阻塞状态等待被唤醒典型synchronized
锁升级
Java的synchronized有锁升级的机制 synchronized自适应进行升级的过程保证了JVM不盲目加锁浪费资源在锁竞争缓和的情况下线程不阻塞浪费时间及时获取到锁在锁竞争激烈的情况下让线程阻塞减轻CPU负担。
CAS
CASCompare And Swap。顾名思义先比较再交换CAS涉及到三个数据这里可以理解为value修改前读取到的值主内存中的共享变量、exceptValue上一次修改后保存的值线程里的局部变量、swapValue要修改的值线程里的局部变量。定义为exceptValue是因为CAS期待exceptValue修改为swapValue。 CAS的工作机制
读取内存值value。比较 value 和exceptValue。如果相等则写入 swapValue否则CAS失败。
这三步操作是不可分割的也就是说一次CAS是原子性的。 exceptValue的值在CAS成功后被更新为value否则保持原值不变。
CAS引发的ABA问题
假设原值为A要修改为B。如果存在多个线程要执行这个修改操作
一个线程修改成功后由于缓存一致性协议value变化时其他线程已经读取的value也会被强制刷新为最新值。多个线程进行CASAAB时一个成功后其他的线程会CAS失败不会对A重复CASAAB。但是CAS只检查值不关心在修改这个值之前是否发生了A-B-…-A这种骚操作。由此引发了ABA问题。例如 线程t1要将A修改为B而线程t2要将A修改为B再修改为A 如果t2先执行完这两个CAS操作t1由于缓存一致性协议会实时读取到最新值所以t1在CAS前里面的A变为B再变为At1执行CAS两次线程结束后最终结果为B。 虽然上面这ABA操作看起来没问题但极端情况下却容易出问题 我有200块存款要取100块钱 我来到ATM插卡输密码设置取100块再点击确定恰好系统超时没有及时吐钱我恼怒的再摁了一下由此产生了两个线程t1t2。两个线程都要CAS200200,100正常情况下总有一个成功而另一个失败但是又恰好老妈打给我100块生活费产生线程t3在t2完成CAS后t3又来一波CAS100,100,200加了100块没有t3t1应该是CAS100,200,100但现在成了CAS200,200,100于是ATM再吐100块。 这是不符合实际的现实生活即便手痒快速摁几下也不会重复响应。 解决方案
为此基于CAS只比较新旧值的特性引入了版本号版本号是一个单调递增的常量每次CAS就1这样即便A-B-A也能因为版本号而察觉到。
原子类
针对多线程操作共享变量例如例如变量自增这种而不发生线程不安全问题可以使用原子类。原子类底层使用了CAS可以保证变量操作的原子性常见的原子类有AtomicInteger、AtomicIntegerArray、AtomicBoolean、AtomicLong、AtomicReference、AtomicStampedReference。举例AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;public class Main {public static void main(String[] args) {AtomicInteger i new AtomicInteger();i.addAndGet(1); //对于int/Integer i相当于i1System.out.println(i);i.decrementAndGet();//相当于--iSystem.out.println(i);i.getAndDecrement();//相当于i--System.out.println(i);i.incrementAndGet();//相当于iSystem.out.println(i);i.getAndIncrement();//相当于iSystem.out.println(i);}
}
公平/不公平锁
Java对公平锁的定义是先来后到哪个线程先抢锁就哪个线程拿锁所以非公平锁就是所有锁竞争者获得锁的机会均等操作系统随机调度线程。
可重入锁
一个线程执行到同步代码块给自己加了锁在这个代码块里又给自己加了锁造成了死锁这个线程就自己阻塞自己锁也释放不了。如果允许线程给自己多次加锁但又不会发生死锁同步代码块执行完后锁正常释放那就是可重入锁。 学Java的同学不用担心死锁的问题只要以Reentrant开头命名的锁都是可重入锁而且JDK提供的所有现成的Lock实现类包括synchronized关键字锁都是可重入的。 ReentrantLock
和synchronized同级别是在Java5以后引入的,相比于synchronized对锁的使用更灵活也更复杂。以下是同synchronized的对比
synchronizedReentrantLock实现方式synchronzied是关键字实现机制在JVM内部需要手动解锁ReentrantLock是Java.util.concurrent.locks.ReentrantLock里的类是在JVM外部实现的而ReentrantLock需要手动unlock()解锁锁竞争处理线程锁竞争失败会一直阻塞不会阻塞可以通过trylock(TIME)/trylock()返回一个boolean值如果是false代表竞争锁失败调用者可根据这个判断编写竞争失败的代码逻辑锁特性非公平锁默认非公平锁可通过构造方法传入ture成为公平锁等待和唤醒可通过Object类的wait()、notify()进行等待和唤醒但唤醒目标是随机的可通过Condition类指定唤醒某个线程
常用方法lock()、trylock(Time)、unclock()。
import java.util.concurrent.locks.ReentrantLock;public class Main {public static void main(String[] args) {ReentrantLock lockernew ReentrantLock();Thread t1new Thread(()-{for(int i0; i500; i){try{locker.lock();System.out.println(Thread .currentThread().getName()i);}finally{locker.unlock();//未防止忘记释放锁将其放在finally里}}});t1.start();}
}读写锁
多个线程对共享变量的读取是不会发生线程不安全的的如果有写入的情况才会发生:
所有线程只读线程安全所有线程写入线程不安全有读有写线程不安全
当有多个线程操作一个共享变量时对其加锁且对它的读取是不互斥但是写入时只能一个线程持有锁达到
读与读之间不互斥所有线程写入一个线程持有锁其他阻塞有读有写一个线程持有锁其他阻塞
于是我们引入了读写锁ReentrantReadWriteLock类通过ReentrantReadWriteLock.readLock获取一个读锁ReadLock对象通过ReentrantReadWriteLock.writeLock获取一个写锁WriteLock对象。 这两个对象使用lock()、tryLock()、**unlock()**来加锁释放锁。
import java.util.concurrent.locks.ReentrantReadWriteLock;public class Main {static int i0;public static void main(String[] args) {ReentrantReadWriteLock lockernew ReentrantReadWriteLock();Thread t1new Thread(()-{locker.readLock().lock();//readLock是ReentrantReadWriteLock类里RreadLock类型的字段System.out.println(i);locker.readLock().unlock();locker.writeLock().lock();//writeLock是ReentrantReadWriteLock类里WriteLock类型的字段i1;locker.writeLock().unlock();});t1.start();}
}
Callable接口
Callable类似于Runnable接口里面定义了call()方法同run()方法一样对任务进行了包装。 但不同的是Callable接口带有泛型且call()方法带有返回值且不可直接将实现了Callable接口的类对象作为参数直接传入Thread类的构造方法里需要通过FutureTask类将任务提交到线程里。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class Main {public static void main(String[] args) throws InterruptedException, ExecutionException {//返回一个实现了Callable接口的匿名类对象CallableInteger callable (() - {int sum 0;for (int i 1; i 100; i) {sum i;}return sum;});FutureTaskInteger futureTask new FutureTask(callable);//FutureTask实现了RunnableFuture接口RunnableFuture继承了Runnable接口Thread t new Thread(futureTask);t.start();System.out.println(futureTask.get());}
}如果要实现上面同样的功能还需在Main里定义一个static字段类记录sum的值方便任务执行完后来确定任务效果。而通过call()带返回值的属性和new FutureTask.get()方法得到任务结果将任务和线程分开达到高内聚低耦合的目的。
完。