并发编程基础 - synchronized和volatile并发模型对比、从顶层俯瞰juc

目录

一、juc包存的意义【解决了synchronized没有解决的部分问题】

1、能够响应中断2、支持超时3、非阻塞地获取锁

二、juc包个人理解分为三个部分

1、atomic工具包

2、AQS

3、线程池

三、Unsafe类

四、CAS存在的问题和解决方案

1、ABA问题

2、循环时间长开销比较大

3、只能保证一个共享变量的原子操作


    synchronized关键字不仅要解决原子性、可见性和有序性问题,还需要Object(ObjectMonitor)相关方法等周边配置一起才能解决所有的并发问题,虽然性能比较差但是jdk6之后做了大量的优化,性能问题已经解决了,那为什么还要juc(java.util.concurrent)包的存在呢?一方面是juc在jdk5就存在了,另一方面因为juc解决了synchronized中的下列问题:

一、juc包存的意义【解决了synchronized没有解决的部分问题】

1、能够响应中断
2、支持超时
3、非阻塞地获取锁

    但是synchronized中锁解决的所有问题(或者功能),juc包中都必须解决,下面是自己整理的一个对比图:

 

二、juc包个人理解分为三个部分

1、atomic工具包

    基于volatile + CAS机制,实现了AtomicInteger等执行并发的原则操作类,volatile基于内存屏障和HappenBefore原则解决了可见性和有序性问题,而CAS解决了原子性问题。CAS本身基于Unsafe的compareAndSwap以及compareAndSet相关api实现。

2、AQS

    AQS(AbstractQueuedSynchronizer)本身对外的表现为虚拟的双向链表。AQS本身也包含了volatile + CAS机制解决原子性可见性和有序性问题;基于其内部类ObjectCondition(Condition的实现)基于LockSupport实现了类似Object的唤醒和等待的功能,当然LockSupport本身也是基于Unsafe包的等待唤醒api实现。AQS本身是一个抽象的模板方法类,有些具体的实现细节交给了子类,并且提供了公平锁和非公平锁两大类。

3、线程池

    频繁的创建和销毁线程非常地消耗资源,那么需要线程池进行复用和管理。线程池也需要解决任务调度的问题,所以也使用到了AQS的功能,其中核心参数存储任务的队列BlockingQueue(比如常用的LinkedBlockingQueue)基于AQS实现;添加的任务ThreadPoolExecutor内部类Worker也是基于AQS实现。

三、Unsafe类

public final class Unsafe {
	private static final sun.misc.Unsafe theUnsafe;
	static {
		registerNatives();
		Reflection.registerMethodsToFilter(sun.misc.Unsafe.class, new String[]{"getUnsafe"});
		theUnsafe = new sun.misc.Unsafe();
		ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
		// 省略部分类型
		ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
		ADDRESS_SIZE = theUnsafe.addressSize();
	}

	// 构造私有,在静态代码块中初始化完成(非懒加载)
	private Unsafe() {
	}
	// 使用公共方法获取
	@CallerSensitive
	public static sun.misc.Unsafe getUnsafe() {
		Class var0 = Reflection.getCallerClass();
		if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
			throw new SecurityException("Unsafe");
		} else {
			return theUnsafe;
		}
	}

	// 管程的指令
	public native void monitorEnter(Object var1);
	public native void monitorExit(Object var1);
	public native boolean tryMonitorEnter(Object var1);

	// 获取设置
	public native int getInt(Object var1, long var2);
	public native void putInt(Object var1, long var2, int var4);

	// Cas指令
	public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

	// LockSupport依赖的指令
	public native void unpark(Object var1);
	public native void park(boolean var1, long var2);

	// x86底层使用的命令是 加载屏障、存储屏障和全屏障(类似StoreLoadBarrier的全能屏障)
	public native void loadFence();
	public native void storeFence();
	public native void fullFence();
}

    Unsafe类可以说是Juc存在的关键,提供了很多native的方法,直接调用底层指令,不如CAS指令等。在Jdk9之前,Usafe类只允许处理BootstrapClassLoader加载的类,根据双亲委派模型可知我们自己写的类则是使用AppClassLoader或者其子类加载完成,也就是说Jdk9之前我们不能直接使用Unsafe类写自己的工具等(当然juc也比较全了,并且是大师Doug Lea操刀)。

1、CAS(如上compareAndSwapInt)【乐观锁的思想】

// Cas指令
	public final native boolean compareAndSwapInt(Object address, long offset, int expected, int value);

第1个参数一般调用时传入this,则可以找到内存地址;读取传入对象address在内存中偏移量的值与期望的值,如果相等就把x值赋值给offset,并且返回true,否则就取消操作,并返回false。而该native方法,直接调用的底层对应的CAS指令。

2、提供了线程等待和唤醒的方法(park和unpark)

    而在juc包下基本都是使用工具类LockSupport间接进行调研,在并发编程基础 - Thread状态和生命周期中我们也得知,该工具的操作会伴随着线程状态的转换。

park(Long):传入一个等待的时间,若传入0L则一直等待没有超时。此时线程由 RUNNABLE 切换为 WAITINGTIMED_WAITING状态。

unpark():传入当前线程等参数,此时线程由WAITING切换为RUNNABLE状态,TIMED_WAITING时间到了会自动切换。

3、提供了x86底层使用的加载屏障、存储屏障和全屏障【全屏障 类似StoreLoadBarrier】

4、提供了管程相关方法:monitorEnter、monitorExit、tryMonitorEnter

5、该类构造私有化,并且在静态代码块中直接初始化(非懒加载),当所有地方调用时返回同一对象(单利模式)。

四、CAS存在的问题和解决方案

1、ABA问题

    CAS会伴随着while(true)这样的循环重试机制,比如叠加器,根据当前的值0进行+1,判断期望内存值为0则把内存值赋值为1(即:0+1),如果与期望的值不符合则说明内存值被其他线程修改过,则需要获取最新的值+1再重试;当然一般该值会是volatile修饰的读取操作会获取到最新的内存值。

    但是如果内存中的值与期望的值一样,就能说明没有被其他线程修改过吗?当然不是,比如线程B 将值 +1,线程C将值 -1,那么内存值与期望值是相等的,但是曾经被操作过,这就是ABA的问题。像叠加这样的场景允许ABA问题存在,对后续结果没有影响;但是并不是所有的业务场景都允许ABA问题存在,解决的办法也比较简单就是乐观锁的思想,增加版本号

2、循环时间长开销比较大

    自旋CAS本身会消耗一定的CPU资源,长时间不成功消耗CPU资源。

3、只能保证一个共享变量的原子操作

    CAS的第一个参数就是为了获取对象的内存地址的,他不能像管程那样执行的代码片段只要中间状态对外不可见即可。但是我们使用CAS进行操作时,怎么同时处理多个对象的原子性呢,AtomicRefrence类保证了引用对象之间的原子性。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值