JUC高并发
1、谈谈你对volatile的理解
1.1volatile是Java虚拟机提供的轻量级的同步机制
1.1.1保证可见性
在内存模型中,线程将自身需要的对象数据从主机物理内存拷贝到线程工作内存(实际仍在物理内存,虚拟的划分约定),当线程对数据的修改经历三个步骤:
- 从物理内存拷贝数据到工作内存
- 在执行线程中修改数据
- 将数据写回物理内存
数据写完后,需要及时的通知其他需要此对象的线程,“该数据已更改,请重新加载”,这就是数据可见性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8gEIEbLQ-1577715678426)(C:\Users\Kin\AppData\Roaming\Typora\typora-user-images\1577408098733.png)]
验证可见性
class Mydata{
int num=0;
//添加volatile关键字再次测试
//volatile int num=0;
public void initTo10(){
this.num=10;
}
public void numPlusPlus(){
this.num++;
}
}
public class VolatileDemo {
public static void main(String[] args) {
seeOkVolatile();
}
public static void seeOkVolatile(){
Mydata mydata = new Mydata();
new Thread(()->{
System.out.println(String.format("start thread:%s",Thread.currentThread().getName()));
try {
Thread.sleep(3000);
mydata.initTo10();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("end thread:%s",Thread.currentThread().getName()));
}).start();
while (mydata.num==0){
}
System.out.println(mydata.num);
}
}
1.1.2不保证原子性
什么是原子性?
不可分割,保证数据完整一致性,要么成功要么失败
public static void notAtomic(){
Mydata mydata = new Mydata();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for(int j=0;j<1000;j++){
mydata.numPlusPlus();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println("The end result:"+mydata.num);
}
//The end result:9192 输出一个小于10000的随机值
分析:
javap -c Mydata.class//反汇编字节码
public void numPlusPlus();
Code:
0: aload_0
1: dup
//从主内存取值
2: getfield #2 // Field num:I
5: iconst_1
//执行加一操作
6: iadd
//将值写回主内存
7: putfield #2 // Field num:I
10: return
}
当多个线程执行时,拿到的初始值一致,各执执行加一操作,但是当数据写回主内存时,系统对线程执行调度,当A线程写入时B线程被挂起,此时数值更新还没有更新到B线程,紧接着B线程就覆盖刚刚更改的值,这样原子性就被破坏了
如何解决原子性?
- 加sync(重量级)
- 使用class AtomicInteger的方法
class Mydata{
volatile int num=0;
public void initTo10(){
this.num=10;
}
public void numPlusPlus(){
this.num++;
}
AtomicInteger atomicInteger=new AtomicInteger();
public void addAtomic(){
atomicInteger.getAndIncrement();
}
}
public class VolatileDemo {
public static void main(String[] args) {
notAtomic();
}
public static void notAtomic(){
Mydata mydata = new Mydata();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for(int j=0;j<1000;j++){
mydata.numPlusPlus();
mydata.addAtomic();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println("The end result:"+mydata.num);
System.out.println("The Atomic result:"+mydata.atomicInteger);
}
/*
The end result:9254
The Atomic result:10000
*/
为什么AtomicInteger可以解决这个问题?
1.1.3禁止指令重排
内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,作用有二:
- 保证特定操作的执行顺序
- 强制刷出各种CPU中的内存数据,保证某些变量的内存可见性
Volatile变量进行写操作
- 会在写操作后面加入一条store屏障指令,禁止上面的普通写和下面的volatile写重排序
- 将工作内存中的共享变量值刷新回到主内存(getfield),执行写操作
Volatile变量进行读操作
- 在读操作前加入load屏障指令
- 从主内存读取
插入屏障就是住址编译器对屏障前后进行重排
1.2谈谈JMM
-
可见性
-
原子性
-
有序性
在多线程交替执行下,编译器会对指令进行重排,这样会影响最终结果
public class ResortSeqDemo{ int a=0; boolean flag=false; public void method1(){ a=1; flag=true; //多线程中,可以先执行flag,也可能先执行a=1 } public void method2(){ if(flag){//如果先执行flag=true,那么此时a=0 a=a+5; } } }
1.3你在什么地方用过volatile
单例模式
public class SinglestonDemo {
private static SinglestonDemo instance1=null;
private static SinglestonDemo instance2=null;
private static SinglestonDemo instance3=null;
private volatile static SinglestonDemo instance4=null;
public SinglestonDemo() {
System.out.println(Thread.currentThread().getName()+"\t 这是一个构造函数");
}
/**
*@Method
*@Description
* 单机程序单例模式
*@author pandas
*@create 2019/12/27 11:48
*@Param []
*@return volatile_demo.SinglestonDemo
*/
public static SinglestonDemo getInstance1(){
if (instance1==null){
instance1=new SinglestonDemo();
}
return instance1;
}
//synchronized将整个函数阻塞
public static synchronized SinglestonDemo getInstance2(){
if (instance2==null){
instance2=new SinglestonDemo();
}
return instance2;
}
/**
*@Method getInstance3
*@Description
* DCL 双端检测锁机制,如果存在指令重排也有可能出错
* 因为某个线程执行第一次检测时,读取到instance不为null,
* 但是此时instance的引用对象可能没有完全初始化
* instance3=new SinglestonDemo();的三个步骤
* 1、memory=allocate();分配内存空间
* 2、instance(memory);初始化对象
* 3、instance=memory;instance指向分配的地址,此时instance!=null
* 由于2、3之间不存在数据依赖关系,所以如果出现先执行3再执行2就会出现异常
*@author pandas
*@create 2019/12/27 11:39
*@Param []
*@return volatile_demo.SinglestonDemo
*/
public static SinglestonDemo getInstance3(){
if (instance3==null){
synchronized (SinglestonDemo.class){
if (instance3==null){
instance3=new SinglestonDemo();
}
}
}
return instance3;
}
public static SinglestonDemo getInstance4(){
if (instance4==null){
synchronized (SinglestonDemo.class){
if (instance4==null){
instance4=new SinglestonDemo();
}
}
}
return instance4;
}
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(()->{
SinglestonDemo.getInstance1();
SinglestonDemo.getInstance2();
SinglestonDemo.getInstance3();
},String.valueOf(i)).start();
}
}
}
1.4如何保证线程安全
-
工作内存与主内存同步延迟现象导致的可见性问题
可以使用synchronized或volatile关键字解决
-
指令重排导致的可见性问题和有序性问题
利用volatile关键字解决
2、CAS你知道吗
CAS->Unsafe->cas底层原理->ABA->原子应用更新->如何规避?
CAS(atomicInteger.compareAndSet(expect,uodate))比较并交换。如果当前的值与expect期望值一致,就更新update的数据。
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5,2019)+"\t this data is"+atomicInteger);
System.out.println(atomicInteger.compareAndSet(5,2020)+"\t this data is"+atomicInteger);
}
}
//result
true this data is2019
false this data is2019
底层原理
-
自旋锁
-
Unsafe类,本地方法类,属于系统原语,不允许中途被中断,类中的方法直接调用操作系统底层
//AtomicInteger l类 public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates //unsafe是一个native类 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { //获取当前对象内存偏移地址,可以直接操作 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } //通过volatile修饰,保证数据可见性 private volatile int value; } //Atomic调用Unsafe public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } //unsafe类中的方法 public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { //unsafe 获取当前对象,对应偏移量地址中的值 var5 = this.getIntVolatile(var1, var2); //再次去取值进行比较,如果相等直接相加,否则循环去取 //思考?会不会因为多线程抢占导致循环太久? } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
CAS缺点
- while循环时间长,自旋可能会造成额外开销
- 只能保证一个共享变量的原子操作,加锁则可以实现多语句操作
- 引出ABA问题
3、原子类Atomiclntegerde ABA问题谈谈?原子更新引用知道吗?
在多线程中,每个线程的执行时间是不能确定的,假设A线程执行时间为10ns,B线程执行时间为2ns,在同一时间两个线程拿到相同的初始值Y,B线程在10ns内将Y改成了X更新到主内存,又将X重新改成了Y更新到主内存,此时A线程比较主内存发现等于Y。A、B线程都将执行成功,但这个过程存在问题。
如何解决ABA问题
时间戳原子引用
public class ABADemo {
static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
new Thread(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+atomicReference.compareAndSet(100,2019)+"\t"+atomicReference);
},"t2").start();
//ABA解决方案
new Thread(()->{
int stamp=atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t第一次版本号"+stamp);
//暂停3线程
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t第二次版本号"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t第三次版本号"+atomicStampedReference.getStamp());
},"t3").start();
new Thread(()->{
int stamp=atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t第一次版本号"+stamp);
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
boolean b = atomicStampedReference.compareAndSet(100, 203, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"修改成功否:"+b+"/t当前实际版本号"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t当前最新值:"+atomicStampedReference.getReference());
},"t4").start();
}
}
result
t4 第一次版本号1
t3 第一次版本号1
t3 第二次版本号2
t2true 2019
t3 第三次版本号3
t4修改成功否:false/t当前实际版本号3
t4 当前最新值:100
4、集合类不安全问题
ArrayList
public class ArraylistDemo {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
0 [null, 3e1f, fac4]
1 [null, 3e1f, fac4]
9 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd]
4 [null, 3e1f, fac4, bed1, cef7, f47d]
6 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0]
12 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab]
13 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232]
14 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2]
2 [null, 3e1f, fac4, bed1]
3 [null, 3e1f, fac4]
18 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813]
5 [null, 3e1f, fac4, bed1, cef7]
17 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10]
16 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f]
Exception in thread "19" Exception in thread "22" 15 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17]
11 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c]
10 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d]
24 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb]
8 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011]
25 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb, b9fe]
26 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb, b9fe, 01ae]
java.util.ConcurrentModificationException
7 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c]
27 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb, b9fe, 01ae, b9e5]
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
23 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a]
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at ArrayListUnsafe.ArraylistDemo.lambda$main$0(ArraylistDemo.java:19)
at java.lang.Thread.run(Thread.java:748)
21 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a]java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
20 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20]
at java.lang.StringBuilder.append(StringBuilder.java:131)
at ArrayListUnsafe.ArraylistDemo.lambda$main$0(ArraylistDemo.java:19)
at java.lang.Thread.run(Thread.java:748)
29 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb, b9fe, 01ae, b9e5, da18, bab6]
28 [null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb, b9fe, 01ae, b9e5, da18]
故障现象
java.util.ConcurrentModificationException
故障原因
集合类并发修改异常。
如何解决
1、List list=new Vector(),数据保证,并发性能下降
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2、List safelist= Collections.synchronizedList(new ArrayList<>());//包装不安全的list
如何实现?
3、List safelist= new CopeAndWriteArrayList() 写时复制,读写分离思想
public boolean add(E e) {
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//复制原有的Array
Object[] elements = getArray();
//计算长度
int len = elements.length;
//新增一个长度的array
Object[] newElements = Arrays.copyOf(elements, len + 1);
//赋值
newElements[len] = e;
//更改当前array的引用地址
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
//完整实例
public class ArraylistDemo {
public static void main(String[] args) {
//不安全
List<String> list=new ArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(Thread.currentThread().getName()+"\t"+list);
},String.valueOf(i)).start();
}
//安全
List<String> safelist= Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 20; i++) {
new Thread(()->{
safelist.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(Thread.currentThread().getName()+"\t"+safelist);
},String.valueOf(i)).start();
}
List<String> copyOnWriteArrayList= new CopyOnWriteArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
copyOnWriteArrayList.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(Thread.currentThread().getName()+"\t"+copyOnWriteArrayList);
},String.valueOf(i)).start();
}
}
}
ArraySet
底层就是Arraylist
HashSet
底层是HashMap,Hashset关注key而非value
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
HashMap
使用ConcurrentHashMap
5、Java锁
5.1公平锁与非公平锁
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁
多个线程按照申请锁的顺序获取锁
非公平
线程通过抢占的方式尝试获取锁,如果抢占失败就采用类似公平锁方式
优点:吞吐量比公平锁大
缺点:有可能造成 优先级反转或饥饿现象
5.2可重入锁/递归锁
指的是同一线程外层函数获取锁之后,内层递归函数任然能获取改锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
public class ReentranLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
System.out.println("-------");
},"t1").start();
new Thread(()->{
phone.sendSms();
System.out.println("-------");
},"t2").start();
new Thread(()->{
phone.sendSms_lock();
System.out.println("-------");
},"t3").start();
new Thread(()->{
phone.sendSms_lock();
System.out.println("-------");
},"t4").start();
}
}
class Phone{
//synchronized 可重入锁
public synchronized void sendSms(){
System.out.println(Thread.currentThread().getId()+"\t invoke sendSms()");
sendEmail();
}
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getId()+"\t invoke sendEmail()");
}
//ReentrantLock 可重入锁
Lock lock=new ReentrantLock();
public synchronized void sendSms_lock(){
lock.lock();
try {
System.out.println(Thread.currentThread().getId()+"\t invoke sendSms()");
sendEmail();
}catch (Exception e){
e.printStackTrace();
}
lock.unlock();
}
public synchronized void sendEmail_lock(){
lock.lock();
try {
System.out.println(Thread.currentThread().getId()+"\t invoke sendEmail()");
}catch (Exception e){
e.printStackTrace();
}
lock.unlock();
}
}
/*
14 invoke sendSms()
14 invoke sendEmail()
-------
15 invoke sendSms()
15 invoke sendEmail()
-------
16 invoke sendSms()
16 invoke sendEmail()
-------
17 invoke sendSms()
17 invoke sendEmail()
-------
*/
作用:避免死锁
5.3自旋锁
尝试获取锁的线程不会立即阻塞(比如wait),而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
//unsafe类中的自旋锁应用
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//unsafe 获取当前对象,对应偏移量地址中的值
var5 = this.getIntVolatile(var1, var2);
//再次去取值进行比较,如果相等直接相加,否则循环去取
//思考?会不会因为多线程抢占导致循环太久?
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t try get lock");
while (!atomicReference.compareAndSet(null,thread));{
}
System.out.println(Thread.currentThread().getName()+"\t get lock");
}
public void muUnlock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t unlock");
atomicReference.compareAndSet(thread,null);
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
System.out.println("<<<<<");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(">>>>>");
spinLockDemo.muUnlock();
},"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
spinLockDemo.myLock();
System.out.println("-----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.muUnlock();
},"t2").start();
}
}
/*
t1 try get lock
t1 get lock
<<<<<
t2 try get lock
>>>>>
t1 unlock
t2 get lock
-----
t2 unlock
*/
5.4独占锁、共享锁、读写锁
独占锁:该锁一次只能被一个线程所有
共享锁:该锁可以被多个线程所有
读写锁:读锁可以被共享,写锁必须独占,保证一致性与并发性
class myCache{
private volatile HashMap<String,Object> map=new HashMap<>();
private ReentrantReadWriteLock rwlock=new ReentrantReadWriteLock();
public void put(String key,Object value){
System.out.println("start write:"+key);
map.put(key, value);
System.out.println("end write");
}
public void get(String key){
System.out.println("start read");
Object o = map.get(key);
System.out.println("end read:"+o);
}
public void put_rw(String key,Object value){
try {
rwlock.writeLock().lock();
System.out.println("rw start write:"+key);
TimeUnit.MILLISECONDS.sleep(300);
map.put(key, value);
System.out.println("rw end write");
} catch (Exception e) {
e.printStackTrace();
}finally {
rwlock.writeLock().unlock();
}
}
public void get_rw(String key){
try {
rwlock.readLock().lock();
System.out.println("rw start read");
TimeUnit.MILLISECONDS.sleep(300);
Object o = map.get(key);
System.out.println("rw end read:"+o);
} catch (Exception e) {
e.printStackTrace();
}finally {
rwlock.readLock().unlock();
}
}
}
public class ReadWriteLock {
public static void main(String[] args) throws InterruptedException {
myCache myCache = new myCache();
for (int i = 0; i <5 ; i++) {
final int index=i;
new Thread(()->{
myCache.put(String.valueOf(index),index);
myCache.get(String.valueOf(index));
}).start();
}
TimeUnit.SECONDS.sleep(1);
for (int i = 0; i <5 ; i++) {
final int index=i;
new Thread(()->{
myCache.put_rw(String.valueOf(index),index);
myCache.get_rw(String.valueOf(index));
}).start();
}
}
}
/*
//没有锁的情况出现了加塞现象
start write:0
start write:2
end write
start write:1
end write
start read
start read
end read:2
end write
start write:4
start write:3
end write
start read
end read:1
end read:3
end write
start read
end read:4
start read
end read:0
//加锁后写入过程被严格控制
rw start write:0
rw end write
rw start write:1
rw end write
rw start write:2
rw end write
rw start write:3
rw end write
rw start write:4
rw end write
//加锁读过程可以实现并发
rw start read
rw start read
rw start read
rw start read
rw start read
rw end read:3
rw end read:2
rw end read:0
rw end read:4
rw end read:1
*/
5.5synchronized与lock的区别?使用新的lock有什么好处?
1、原始构成
synchronized是关键字属于JVM层面,monitorenter(底层是通过monitor对象来完成,wait、notify等方法也依赖与monitor对象,只有在 同步块或方法中才能调用wait、notify等方法monitorexit)
lock是具体类属于API层面
2、使用方法
synchronized不需要手动释放,执行完成会自动释放
ReentranLock必须手动释放锁
3、等待是否可中断
synchronized不可中断,除非抛出异常或正常结束
ReentranLockkeh可中断,设置超时方法tryLock(long timeout,TimeUnit unit),将lockInterruptibly()放入代码块中调用interrupt()方法触发中断。
4、加锁是否公平
synchronized非公平锁
ReentranLock两者都可以
5、锁绑定多个条件Condition
synchronized没有,要么随机唤醒一个,要么全部唤醒
ReentranLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒
题目:多线程之间顺序调用,实现A->B->C的顺序启动,要求如下:
AA打印3次,BB打印2次,CC打印3次;
循环5次
class ShareOPtion{
private int number=0;
private ReentrantLock lock=new ReentrantLock();
private Condition[] conditions;
public ShareOPtion(int thread) {
conditions=new Condition[thread];
for (int i = 0; i < thread; i++) {
conditions[i]=lock.newCondition();
}
}
public void option(int thread,int printNum){
try {
lock.lock();
while (number!=thread){
conditions[thread].await();
}
number=(thread+1)%3;
for (int i = 0; i < printNum; i++) {
System.out.print(Thread.currentThread().getName()+"\t"+i+"\t");
}
System.out.println();
conditions[(thread+1)%3].signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class SynchronizedDemo {
public static void main(String[] args) {
ShareOPtion shareOPtion = new ShareOPtion(3);
for (int i = 0; i < 5; i++) {
new Thread(()->{
shareOPtion.option(0,3);
},"AA").start();
new Thread(()->{
shareOPtion.option(1,2);
},"BB").start();
new Thread(()->{
shareOPtion.option(2,3);
},"CC").start();
}
}
}
/*
AA 0 AA 1 AA 2
BB 0 BB 1
CC 0 CC 1 CC 2
AA 0 AA 1 AA 2
BB 0 BB 1
CC 0 CC 1 CC 2
AA 0 AA 1 AA 2
BB 0 BB 1
CC 0 CC 1 CC 2
AA 0 AA 1 AA 2
BB 0 BB 1
CC 0 CC 1 CC 2
AA 0 AA 1 AA 2
BB 0 BB 1
CC 0 CC 1 CC 2
*/
来源:CSDN
作者:苍鹰嘉措
链接:https://blog.csdn.net/weixin_45075077/article/details/103758253